Documentation Index
Fetch the complete documentation index at: https://woltz.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
What are Domain Events?
Domain Events represent something significant that happened in your domain. They enable loose coupling between aggregates and support event-driven architectures.
The Rich Domain core library provides interfaces only - you implement the event publishing mechanism according to your needs (in-memory, message queues, event streams, etc.).
import { DomainEvent } from "@woltz/rich-domain";
type OrderConfirmedPayload = {
customerId: string;
total: number;
};
export class OrderConfirmedEvent extends DomainEvent<OrderConfirmedPayload> {}
Creating Events
Basic Event
Events are created by extending DomainEvent with a typed payload:
type UserCreatedPayload = {
email: string;
};
export class UserCreatedEvent extends DomainEvent<UserCreatedPayload> {}
Event with Queue Name (Optional)
You can specify a static queueName for routing events to specific queues (useful for message queue implementations):
type SendEmailPayload = {
to: string;
subject: string;
body: string;
};
export class SendEmailNotification extends DomainEvent<SendEmailPayload> {
static readonly queueName = "notification-events";
}
Event Properties
Every domain event automatically has these properties:
| Property | Type | Description |
|---|
eventId | string | Unique identifier for this event occurrence |
eventName | string | Name of the event (class name) |
occurredOn | Date | When the event occurred |
payload | P | The event data (typed) |
queueName | string | Optional queue name (static property) |
const event = new UserCreatedEvent({
email: "john@example.com",
});
console.log(event.eventId); // "1699876543210-abc123def"
console.log(event.eventName); // "UserCreatedEvent"
console.log(event.occurredOn); // 2024-12-27T10:30:00.000Z
console.log(event.payload); // { email: "john@example.com" }
Raising Events from Aggregates
Use addDomainEvent() inside aggregate methods to record events:
import { Aggregate } from "@woltz/rich-domain";
class User extends Aggregate<UserProps> {
static create(props: Omit<UserProps, "id">): User {
const user = new User(props);
// Add event - it will be stored in the aggregate
user.addDomainEvent(
new UserCreatedEvent({
email: user.email,
})
);
return user;
}
activate() {
if (this.props.status === "active") return;
this.props.status = "active";
this.addDomainEvent(
new UserActivatedEvent({ userId: this.id.value })
);
}
changeEmail(newEmail: string) {
const oldEmail = this.props.email;
this.props.email = newEmail;
this.addDomainEvent(
new UserEmailChangedEvent({ oldEmail, newEmail })
);
}
}
Event Bus Interface
Rich Domain provides the IDomainEventBus interface with two methods:
interface IDomainEventBus {
/**
* Publish a single domain event
*/
publish(event: IDomainEvent): Promise<void>;
/**
* Publish multiple domain events
*/
publishAll(events: IDomainEvent[]): Promise<void>;
}
You implement this interface according to your infrastructure needs.
Publishing Events
From Aggregates
After persisting changes, dispatch all uncommitted events:
import { BullMQEventBus } from "./infrastructure/event-bus";
// In your service/use case
async function createUser(data: CreateUserInput) {
// 1. Create aggregate (events are recorded)
const user = User.create(data);
// 2. Save to database
await userRepository.save(user);
// 3. Dispatch all uncommitted events (clears automatically)
await user.dispatchAll(eventBus);
return user;
}
Direct Publishing
You can also publish events directly without aggregates:
// Single event
const event = new UserCreatedEvent({ email: "john@example.com" });
await eventBus.publish(event);
// Multiple events
const events = [
new UserCreatedEvent({ email: "john@example.com" }),
new SendEmailNotification({
to: "john@example.com",
subject: "Welcome!",
body: "Thanks for joining!",
}),
];
await eventBus.publishAll(events);
Managing Uncommitted Events
Aggregates and entities provide methods to manage events:
const user = User.create({ email: "john@example.com" });
// Check if there are uncommitted events
console.log(user.hasUncommittedEvents()); // true
// Get all uncommitted events
const events = user.getUncommittedEvents();
console.log(events); // [UserCreatedEvent]
// Clear all events
user.clearEvents();
console.log(user.hasUncommittedEvents()); // false
Event Serialization
Events can be serialized to JSON for storage or transmission:
const event = new UserCreatedEvent({ email: "john@example.com" });
const json = event.toJSON();
console.log(json);
// {
// eventId: "1699876543210-abc123def",
// eventName: "UserCreatedEvent",
// occurredOn: "2024-12-27T10:30:00.000Z",
// payload: {
// email: "john@example.com"
// }
// }
API Reference
DomainEvent
abstract class DomainEvent<P> implements IDomainEvent<P> {
readonly eventId: string;
readonly occurredOn: Date;
readonly payload: P;
static readonly queueName?: string;
constructor(payload: P);
get eventName(): string;
toJSON(): object;
}
IDomainEventBus
interface IDomainEventBus {
publish(event: IDomainEvent): Promise<void>;
publishAll(events: IDomainEvent[]): Promise<void>;
}
Aggregate Methods
class BaseAggregate {
protected addDomainEvent(event: IDomainEvent): void;
getUncommittedEvents(): IDomainEvent[];
clearEvents(): void;
hasUncommittedEvents(): boolean;
async dispatchAll(bus: IDomainEventBus): Promise<void>;
getEvent(eventName: string): IDomainEvent | undefined;
hasEvent(eventName: string): boolean;
}