@jcarrillosrc/cqrs
v1.0.5
Published
## Introduction
Readme
CQRS Library for TypeScript
Introduction
This library provides an implementation of the Command Query Responsibility Segregation (CQRS) pattern in TypeScript. CQRS is a design pattern that separates the responsibility of handling commands (actions that change state) from queries (actions that retrieve state). This separation improves scalability, maintainability, and security in complex applications.
Features
- CommandBus: Handles commands that modify the system state.
- QueryBus: Handles queries that retrieve information from the system.
- EventBus: Manages events to notify different parts of the system about changes.
- Middleware Support: All buses support middleware configuration to extend functionality such as logging, validation, or security checks.
- Domain Events: Built-in support for domain events through EventStore.
- Code Generation: Tools to generate custom bus definitions.
Installation
npm install @jcarrillosrc/cqrsUsage
Command Bus
The Command Bus handles operations that modify the system state. Commands represent intentions to change the system (like creating a user or updating a record). Each command must have exactly one handler, and handlers should not return values.
class MyFirstCommand {
constructor(public readonly value: string){}
}
class MyFirstCommandHandler implements CommandHandler<FooCommandStub> {
handle(command: MyFirstCommand): Promise<void> {
console.log('Handled command with value:', command.value);
}
}
const bus = new CommandBus();
bus.register(MyFirstCommand, new MyFirstCommandHandler());
const message = new MyFirstCommand();
await bus.dispatch(message);Query Bus
The Query Bus manages operations that retrieve data from the system. Unlike commands, queries don't modify state and always return a response. They are used to fetch and return data in a specific format.
class MyFirstQuery {
constructor(public readonly id: string){}
}
class MyFirstQueryResponse {
constructor(public readonly data: string){}
}
class MyFirstQueryHandler implements QueryHandler<MyFirstQuery, MyFirstQueryResponse> {
handle(query: MyFirstQuery): Promise<MyFirstQueryResponse> {
return Promise.resolve(new MyFirstQueryResponse(`Data for id: ${query.id}`));
}
}
const bus = new QueryBus();
bus.register(MyFirstQuery, new MyFirstQueryHandler());
const query = new MyFirstQuery('123');
const response = await bus.dispatch(query);
console.log(response.data); // Outputs: "Data for id: 123"Event Bus and Domain Events
The Event Bus manages the publication and handling of domain events. Events represent something that has already happened in the system. Multiple listeners can subscribe to the same event type.
Basic Event Handling
class MyFirstEvent {
constructor(public readonly eventData: string){}
}
class MyFirstEventListener implements EventListener<MyFirstEvent> {
handle(event: MyFirstEvent): Promise<void> {
console.log('First listener handled event with data:', event.eventData);
return Promise.resolve();
}
}
const bus = new EventBus();
bus.register(MyFirstEvent, new MyFirstEventListener());
await bus.publish(new MyFirstEvent('Something happened!'));Event Bus Execution Strategies
The EventBus supports three execution strategies for handling multiple listeners:
// Sequential execution
const syncBus = EventBus.createSync();
// Parallel execution (fails if any listener fails)
const allBus = EventBus.createAll();
// Parallel execution (continues even if there are failures)
const allSettledBus = EventBus.createAllSettled();Domain Events with EventStore
class CreateUserCommand {
constructor(public readonly userId: string) {}
}
class UserCreatedEvent {
constructor(public readonly userId: string) {}
}
class CreateUserCommandHandler implements CommandHandler<CreateUserCommand> {
async handle(command: CreateUserCommand, eventStore: EventStore): Promise<void> {
// User creation logic
eventStore.record(new UserCreatedEvent(command.userId));
}
}
// Configure middleware to dispatch events
const eventBus = EventBus.create();
const commandBus = CommandBus.create({
middlewareStack: new MiddlewareStack([
new DomainEventsDispatcherMiddleware(eventBus)
])
});Middleware Support
The middleware pattern allows you to create a chain of operations that wrap around the handling of messages. Each middleware in the chain has the responsibility to execute the next middleware until the final handler is reached.
class LoggingMiddleware extends Middleware<any> {
async execute(message: any, handle: Handler<any, any>, stack: MiddlewareStack<any>, eventStore: EventStore): Promise<any> {
console.log('Before handling:', message.constructor.name);
const result = await stack.next().execute(message, handle, stack, eventStore);
console.log('After handling:', message.constructor.name);
return result;
}
}
// Configure middleware stack for any bus
const bus = new CommandBus(
new MiddlewareStack([
new LoggingMiddleware(),
// Add as many middleware as needed
])
);Return Types
- CommandBus: Always returns void
- QueryBus: Returns the type specified by the handler
- EventBus: No guaranteed return value (depends on execution strategy)
