Skip to main content

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:
PropertyTypeDescription
eventIdstringUnique identifier for this event occurrence
eventNamestringName of the event (class name)
occurredOnDateWhen the event occurred
payloadPThe event data (typed)
queueNamestringOptional 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;
}