Domain Events
Introduction
Section titled “Introduction”A domain event records a meaningful change in a domain, such as an order being confirmed. Xeno.JS describes its structure with IDomainEvent<TPayload, TId>, a typed contract for the event data and the aggregate metadata associated with it.
The Problem
Section titled “The Problem”Passing only an event name or an untyped payload makes it difficult for consumers to know which aggregate produced the event, which revision it represents, and what data it carries. A shared event shape makes those parts explicit while allowing each event payload to remain specific to its domain.
The Principle
Section titled “The Principle”Describe an event as a fact that has occurred, and include only the data its consumers need to understand that fact. Keep the payload type aligned with the event’s meaning.
Events belong to the domain model. The event interface does not define persistence, dispatch, or delivery guarantees. Aggregate behavior creates domain events; application and infrastructure workflows decide how to store or deliver them.
The Mechanism
Section titled “The Mechanism”IDomainEvent<TPayload, TId> is an interface with readonly fields:
aggregateId: UniqueId<TId>identifies the aggregate associated with the event;aggregateType: stringidentifies the aggregate type;eventType: stringnames the event;version: numberidentifies the event’s position in the aggregate’s version sequence;occurredAt: Daterecords when the event occurred;payload: TPayloadcontains event-specific data;destination?: stringoptionally identifies a destination for a consuming workflow.
TPayload defaults to unknown. Specify a payload type to retain type safety when reading event data. TId defaults to string and types the aggregate identifier; use a literal kind such as 'Order' to distinguish aggregate ID types in TypeScript.
The readonly modifiers prevent assigning to these properties through the interface in TypeScript. They do not freeze event objects at runtime.
Type and Consume an Event
Section titled “Type and Consume an Event”Define the payload for an event and specialize IDomainEvent with the aggregate kind. Consumers can then use the event’s metadata and payload without treating the payload as unknown:
import type { IDomainEvent } from '@xeno-js/shared'
interface OrderConfirmedPayload { orderNumber: string customerId: string}
type OrderConfirmedEvent = IDomainEvent<OrderConfirmedPayload, 'Order'>
function describeOrderConfirmation(event: OrderConfirmedEvent): string { return `${event.payload.orderNumber} was confirmed at ${event.occurredAt.toISOString()}`}In normal aggregate workflows, the aggregate root creates the complete event. Its raise() method supplies aggregateId, increments and assigns version, sets occurredAt, applies the event to the aggregate, and records it as uncommitted. The aggregate’s apply() implementation handles the state transition. See Aggregate Roots for a complete example.
Event Data and Delivery
Section titled “Event Data and Delivery”An event describes a domain fact; it does not itself publish that fact. IDomainEvent has no methods and does not specify a transport, persistence format, retry policy, or delivery guarantee. The application and infrastructure layers handle those concerns through their own workflows.
destination is optional metadata in the event contract. The interface does not define its format or enforce how a consumer uses it. Treat it according to the conventions of the workflow that reads it.
Behavior
Section titled “Behavior”- The interface provides a typed shape; it does not create an event or perform runtime validation.
payloadis generic and defaults tounknown.aggregateIdis aUniqueId<TId>, withTIdserving as a compile-time kind marker.versionandoccurredAtare part of the event contract;AggregateRoot.raise()assigns them when it creates a new event.destinationis optional.- The interface does not define publishing, persistence, or delivery behavior.
Related Documentation
Section titled “Related Documentation”Support Us
Section titled “Support Us”Xeno.JS is an MIT-licensed open source project. It can grow thanks to the support of these awesome people. If you’d like to join them, please read more at support section.
