> ## Documentation Index
> Fetch the complete documentation index at: https://woltz.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Domain Events

> Communicate changes across aggregates with domain events

## 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.).

```typescript theme={null}
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:

```typescript theme={null}
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):

```typescript theme={null}
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)       |

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
// 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:

```typescript theme={null}
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:

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
interface IDomainEventBus {
  publish(event: IDomainEvent): Promise<void>;
  publishAll(events: IDomainEvent[]): Promise<void>;
}
```

### Aggregate Methods

```typescript theme={null}
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;
}
```
