Domain Events and AggregateRoot
Emit and publish domain events from aggregate roots using the EventBus and mergeObjectContext.
Why Domain Events?
In an event-driven CQRS system, a domain event records something meaningful that already happened inside your domain: OrderPlaced, PaymentCaptured, UserDeactivated. They are named in the past tense because they are facts, not requests.
Domain events let you decouple side effects from the core write logic. Instead of an order service directly calling email, inventory, and analytics code, it simply emits OrderPlaced, and independent handlers react.
- Command: an intent to change state (
PlaceOrderCommand). - Event: a record of a change that occurred (
OrderPlacedEvent).
NestJS provides first-class support for this through @nestjs/cqrs, the AggregateRoot base class, and the EventBus.
The AggregateRoot Base Class
An aggregate root is the entry point to a cluster of domain objects that change together and must stay consistent. In @nestjs/cqrs, you make a class an aggregate by extending AggregateRoot.
This base class gives you two key methods:
apply(event)— record a domain event that happened in this aggregate.commit()— publish all recorded events to theEventBus.
The aggregate buffers events internally until you explicitly commit them. This separation lets you mutate state first and publish only after the change is safely persisted.
import { AggregateRoot } from '@nestjs/cqrs';
export class OrderPlacedEvent {
constructor(
public readonly orderId: string,
public readonly total: number,
) {}
}
export class Order extends AggregateRoot {
constructor(public readonly id: string) {
super();
}
place(total: number) {
// mutate state here, then record the fact
this.apply(new OrderPlacedEvent(this.id, total));
}
}All lessons in this course
- Commands, Handlers, and the Command Bus
- Queries and Read-Model Projections
- Domain Events and AggregateRoot
- Sagas for Long-Running Workflows