CQRS Pattern: CommandBus, QueryBus, EventBus, and Sagas
- Last updated
- Prerequisites:
- NestJS Services and Dependency Injection
- CQRS & Event Sourcing patterns
- nestjs
- cqrs
- command-bus
- query-bus
- event-bus
- sagas
- architecture
Read at your depth
The practical view
The CQRS package (`@nestjs/cqrs`) separates read and write operations. **Commands** represent state-changing actions executed via `CommandBus.execute(new CreateUserCommand(data))`. **Queries** represent read-only operations executed via `QueryBus.execute(new GetUserQuery(id))`. **Events** announce past occurrences via `EventBus.publish(new UserCreatedEvent(id))`. **Sagas** listen to events and trigger subsequent commands asynchronously.
The same idea in other frameworks
`commandBus.execute(new CreateUserCommand())` and `@CommandHandler(CreateUserCommand)`.Axon Framework `@CommandHandler` and `@QueryHandler` buses in Java.MediatR `IMediator.Send(new CreateUserCommand())` in C# .NET.Watermill / custom Go channel-based Command/Query message buses.Legacy vs modern
Monolithic Service with Mixed Read/Write vs Clean CQRS Handlers
Monolithic services become bloated with hundreds of mixed methods; CQRS breaks features into isolated command and query handlers.
userService.createAndEmailAndLogAndAudit(data); // ❌ 2000-line service god objectcommandBus.execute(new CreateUserCommand(dto)); // ✅ NEW: Single-responsibility handlerInterview gotchas
Context
Interviewers test your knowledge of event-driven distributed transactions.
The mechanical answer
An **Event Handler** (`@EventsHandler`) is a terminal listener that performs side effects (e.g. sending an email or writing a log) when an event occurs. A **Saga** (`@Saga()`) is an RxJS stream operator that listens to events and **translates them into NEW Commands** to be dispatched to the `CommandBus`. Sagas orchestrate multi-step, asynchronous business workflows (e.g. `OrderCreatedEvent` -> maps to -> `ExecutePaymentCommand`).
Trap
A Saga must return an `Observable<ICommand>`. If a saga returns `void`, it will not dispatch commands.
Context
Checks precision regarding Command vs Event semantics in CQRS.
The mechanical answer
**No.** In CQRS, a **Command has exactly ONE handler** (1:1 relationship) because a command represents an explicit intent to mutate state. An **Event can have ZERO or MANY handlers** (1:N broadcast relationship) because an event represents a past fact that multiple components may react to.
Trap
Registering two handlers for the same `ICommand` will throw a runtime error in NestJS.