Architectural · Data & Consistency · DWG NO. 055

Domain Events

Represent something meaningful that happened inside a single bounded context as a small, in-process event, distinct from cross-service integration events.

TypeData & Consistency
ScopeWithin one bounded context
ComplexityLow–Medium
Common inDDD-style applications, decoupling side effects from core logic

A domain method doing five unrelated things because they all "happen after"

An `Order.complete()` method that also sends a confirmation email, updates a loyalty point balance, and notifies a warehouse system directly is now responsible for things far outside what "completing an order" should mean — and every one of those side effects makes the method harder to test and more likely to break for unrelated reasons.

The domain raises an event; something else reacts

When something meaningful happens inside the domain, the entity raises a domain event (`OrderCompleted`) recording that fact — without deciding what should happen next. Separate handlers, subscribed to that event, carry out the side effects. This is the in-process cousin of Publisher-Subscriber, scoped to a single application rather than across services.

Order.complete()raisesOrderCompleted(domain event)EmailLoyaltyWarehouse

An entity raising events, handled outside its own method

`Order.complete()` only records that completion happened. Everything that should follow from it lives in separate, independently testable handlers.

domain-events.js
class Order {
  #events = [];
  complete() {
    this.status = "completed";
    this.#events.push({ type: "OrderCompleted", orderId: this.id });
  }
  pullEvents() {
    const events = this.#events;
    this.#events = [];
    return events;
  }
}

const handlers = {
  OrderCompleted: [
    (e) => console.log(`email: confirmation sent for ${e.orderId}`),
    (e) => console.log(`loyalty: points added for ${e.orderId}`),
  ],
};

function dispatch(events) {
  for (const event of events) {
    for (const handler of handlers[event.type] ?? []) handler(event);
  }
}

const order = new Order();
order.id = "order-1";
order.complete(); // Order itself does nothing but record the fact

dispatch(order.pullEvents()); // side effects happen here, decoupled from Order

Trade-offs