Skip to content

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");
});

See it on GitHub

Released under the MIT License.