@nestjs/cqrs
repository·master·Indexed 21 days ago
https://github.com/nestjs/cqrsA lightweight Command Query Responsibility Segregation (CQRS) module for the NestJS framework (node.js) version 11.0.3. It provides core abstractions for Command and Query patterns, including CommandBus, QueryBus, and EventBus for dispatching messages. The module includes decorators for CommandHandlers, QueryHandlers, EventHandlers, and Sagas, as well as tools for managing domain aggregates via AggregateRoot and EventPublisher.
What's inside @nestjs/cqrs
- For a comprehensive overview and a step-by-step tutorial on implementing the Command Query Responsibility Segregation (CQRS) pattern within the NestJS framework, refer to the official NestJS documentation.
Install @nestjs/cqrs
masterTo use the CQRS module in your NestJS application, install the
@nestjs/cqrspackage using npm.$ npm install --save @nestjs/cqrsUse EventPublisher to enable event publishing on AggregateRoots
masterThe
EventPublisherclass is used to inject event publishing capabilities into your AggregateRoot classes or instances. Because the baseIAggregateRootinterface only defines the shape of the methods, you must useEventPublisherto actually bind thepublishandpublishAllmethods to your aggregates so they can communicate with theEventBus.There are two ways to apply this:
mergeClassContext: Used during the definition of an aggregate class to wrap the class definition with the publishing logic.mergeObjectContext: Used on a specific instance of an aggregate to attach the publishing methods directly to that object.
// Example using mergeClassContext for a class definition @Injectable() export class MyService { constructor(private readonly eventPublisher: EventPublisher) {} createAggregate() { // Wrap the AggregateRoot class with the publisher context const MyAggregate = this.eventPublisher.mergeClassContext(MyAggregateRoot); return new MyAggregate(); } } // Example using mergeObjectContext for an existing instance @Injectable() export class MyService { constructor(private readonly eventPublisher: EventPublisher) {} getAggregate() { const aggregate = new MyAggregateRoot(); // Attach publish/publishAll methods to the instance return this.eventPublisher.mergeObjectContext(aggregate); } }Configure the CommandBus publisher
masterThe
CommandBususes anICommandPublisherto publish commands. By default, it usesDefaultCommandPubSub, which is an in-memory implementation.You can customize the publisher in two ways:
- Via Module Options: Provide a
commandPublisherin theCqrsModuleOptionsduring module initialization. - Directly on the Instance: Use the
publishersetter to replace the current publisher on an existingCommandBusinstance.
// Setting a custom publisher directly commandBus.publisher = myCustomPublisher;- Via Module Options: Provide a
Use CQRS specific exceptions
masterThe
@nestjs/cqrspackage exports several specialized exceptions used to identify errors within the Command Query Responsibility Segregation pattern. These exceptions are typically thrown by the internal CQRS bus when handlers or sagas are misconfigured or missing.Available exceptions include:
CommandNotFoundException: Thrown when a command is dispatched but no handler is registered for it.QueryNotFoundException: Thrown when a query is dispatched but no handler is registered for it.InvalidCommandHandlerException: Thrown when a command handler is invalid.InvalidQueryHandlerException: Thrown when a query handler is invalid.InvalidEventsHandlerException: Thrown when an event handler is invalid.InvalidSagaException: Thrown when a saga is invalid.UnsupportedSagaScopeException: Thrown when a saga is used with an unsupported scope.
Access CQRS Commands and Queries
masterThe
@nestjs/cqrspackage provides core abstractions for Command and Query patterns. Use the main entry point to accessCommandandQuerybase classes, which are used to define the intent of operations within your application. Commands represent actions that change state, while Queries represent requests for data.import { Command, Query } from '@nestjs/cqrs';Configure CqrsModule asynchronously
masterTo configure the
CqrsModuleusing asynchronous providers (e.g., fetching configuration from a database or external service), use theCqrsModuleAsyncOptionsinterface. This follows the standard NestJS dynamic module pattern.export interface CqrsModuleAsyncOptions { imports?: any[]; useExisting?: Type<CqrsModuleOptionsFactory>; useClass?: Type<CqrsModuleOptionsFactory>; useFactory?: (...args: any[]) => Promise<CqrsModuleOptions> | CqrsModuleOptions; useValue?: CqrsModuleOptions; inject?: any[]; extraProviders?: Provider[]; }Execute commands with CommandBus
masterThe
CommandBusis the primary entry point for dispatching commands in the CQRS module. You use theexecutemethod to trigger a command, which will then be routed to its registered handler. Theexecutemethod returns aPromisethat resolves with the result returned by the command handler.If no handler is registered for the provided command, it throws a
CommandHandlerNotFoundException.// Assuming 'command' is an instance of a class implementing ICommand const result = await commandBus.execute(command); // You can also pass an optional AsyncContext const result = await commandBus.execute(command, context);Configure the QueryBus publisher
masterThe
QueryBususes anIQueryPublisherto publish queries. By default, it usesDefaultQueryPubSub(an in-memory implementation). You can customize the publisher by either:- Providing a
queryPublisherin theCqrsModuleOptionsduring module configuration. - Using the
publishersetter on theQueryBusinstance directly.
Access the current publisher via the
publishergetter.// Setting a custom publisher directly this.queryBus.publisher = myCustomPublisher;- Providing a
Use the ofType operator to filter events
masterThe
ofTypeoperator allows you to filter events on theEventBusso that a handler only reacts to events of a specific type. This is useful when you want to subscribe to a subset of all events flowing through the bus.To use it, you pass the class constructor of the event type you want to listen for to the
ofTypefunction. When used in conjunction with an event listener, it ensures the callback is only executed for events that are instances of that specific class.// Example usage pattern (conceptual based on export) // eventBus.subscribe(ofType(MySpecificEvent)) // .subscribe(event => { // // This only runs for MySpecificEvent // });Register Sagas with the EventBus
masterSagas are long-running processes that listen to events and trigger commands. You can register multiple sagas at once using
registerSagas, which looks for methods decorated with@Saga(usingSAGA_METADATA). Note that sagas must be part of a static dependency tree to be registered this way; otherwise, anUnsupportedSagaScopeExceptionis thrown.// Registering multiple sagas from an array of wrappers this.eventBus.registerSagas(sagaWrappers); // Registering a single saga function this.eventBus.registerSaga(mySagaFunction);Use the IEventBus interface to publish events
masterThe
IEventBusinterface defines the contract for publishing events within the CQRS module. It supports publishing single events or multiple events simultaneously. You can optionally provide anAsyncContextor a customdispatcherContextto manage execution context during event dispatching.Key methods:
publish(event: TEvent, ...): Dispatches a single event.publishAll(events: TEvent[], ...): Dispatches an array of events.
Both methods support overloads to include
AsyncContextandTContext(dispatcher context) for advanced context propagation.// Example of publishing a single event eventBus.publish(new MyEvent()); // Example of publishing multiple events eventBus.publishAll([new MyEvent(), new AnotherEvent()]); // Example of publishing with context eventBus.publish(new MyEvent(), dispatcherContext, asyncContext);