CQRS & Sagas
Bunstone provides a full implementation of the Command Query Responsibility Segregation (CQRS) pattern.
Registration
To use CQRS features, you must import the CqrsModule in your root AppModule. Since it is a Global Module, the buses will be available for injection in all other modules.
typescript
import { Module, CqrsModule } from "@grupodiariodaregiao/bunstone";
@Module({
imports: [CqrsModule],
})
export class AppModule {}Command Bus
Commands are used to perform actions that change the state of the application.
typescript
// 1. Define Command
class CreateUserCommand implements ICommand {
constructor(public readonly name: string) {}
}
// 2. Define Handler
@CommandHandler(CreateUserCommand)
class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
async execute(command: CreateUserCommand) {
// logic to create user
return { id: 1, name: command.name };
}
}
// 3. Execute
const result = await commandBus.execute(new CreateUserCommand("John"));Query Bus
Queries are used to retrieve data without changing state.
typescript
@QueryHandler(GetUserQuery)
class GetUserHandler implements IQueryHandler<GetUserQuery> {
async execute(query: GetUserQuery) {
return { id: query.id, name: "John" };
}
}Event Bus
Events are used to notify other parts of the system that something has happened.
typescript
@EventsHandler(UserCreatedEvent)
class UserCreatedHandler implements IEventHandler<UserCreatedEvent> {
handle(event: UserCreatedEvent) {
console.log(`User created: ${event.userId}`);
}
}Sagas
Sagas are long-running processes that react to events and can trigger new commands. They use a reactive stream approach.
typescript
@Injectable()
export class UserSaga {
@Saga()
onUserCreated = (events$: IEventStream) =>
events$.pipe(
ofType(UserCreatedEvent),
map((event) => new SendWelcomeEmailCommand(event.email))
);
}Practical Example
For a complete working example of CQRS with commands and handlers, see:
ts
import {
Module,
Controller,
Post,
Body,
AppStartup,
CqrsModule,
CommandBus,
CommandHandler,
} from "../../index";
// 1. Define a Command
class CreateUserCommand {
constructor(public readonly name: string) {}
}
// 2. Define a Command Handler
@CommandHandler(CreateUserCommand)
class CreateUserHandler {
async execute(command: CreateUserCommand) {
console.log(`Executing CreateUserCommand for name: ${command.name}`);
return { id: "123", name: command.name };
}
}
@Controller("users")
class UserController {
constructor(private readonly commandBus: CommandBus) {}
@Post()
async createUser(@Body() body: { name: string }) {
// 3. Dispatch the command via the Bus
return await this.commandBus.execute(new CreateUserCommand(body.name));
}
}
@Module({
imports: [CqrsModule],
controllers: [UserController],
providers: [CreateUserHandler],
})
class AppModule {}
const app = AppStartup.create(AppModule);
app.listen(3000, () => {
console.log("CQRS example is running on http://localhost:3000");
});