Skip to content

Command Bus

The Command Bus is a central piece of DomusJS, enabling you to dispatch commands (intentions to change system state) through a clean, decoupled pipeline.

✅ Separates the sender from the handler.
✅ Supports CQRS (Command Query Responsibility Segregation).
✅ Allows adding middleware (logging, validation, transactions) transparently.


Without a command bus, your code would directly call service methods, tightly coupling layers.

With the command bus:

  • You dispatch a command.
  • The bus finds the registered handler.
  • The handler executes the command logic.

Example:

const result = await commandBus.dispatch(new RegisterUserCommand(email, password));

✅ The sender doesn’t need to know the implementation details.


To use the Command Bus, you need to:

✅ Ensure the CommandBus implementation has been initialized (by default, InMemoryCommandBus).
✅ Register each Command and its associated CommandHandler.

ℹ️ Note:
The CommandBus should have been initialized earlier by calling registerDomusCore().
You can then safely resolve it from the container using container.resolve().


register-user.command.ts
import { Command } from '@domusjs/core';
export class RegisterUserCommand implements Command {
static TYPE = 'REGISTER_USER';
readonly type = RegisterUserCommand.TYPE;
constructor(
public readonly email: string,
public readonly password: string
) {}
}
register-user.handler.ts
import { CommandHandler } from '@domusjs/core';
import { RegisterUserCommand } from './register-user.command';
export class RegisterUserHandler implements CommandHandler<RegisterUserCommand> {
async execute(command: RegisterUserCommand): Promise<void> {
// Business logic: validate, create user, emit events
}
}

DomusJS provides the helper function registerCommandHandler to register the command handler.

import { container } from 'tsyringe';
import { CommandBus } from '@domusjs/core';
import { registerCommandHandler } from '@domusjs/infrastructure';
import { RegisterUserCommand } from './register-user.command';
import { RegisterUserHandler } from './register-user.handler';
const commandBus = container.resolve<CommandBus>('CommandBus');
registerCommandHandler(commandBus, RegisterUserCommand, RegisterUserHandler);
commandBus.dispatch(new RegisterUserCommand('john@example.com', 'secret123'));

✅ The controller only talks to the bus — no need to know the handler.

✅ You can extend functionality (middleware, tracing) without touching the app logic.


DomusJS defines a minimal set of interfaces:

export interface Command {
readonly type: string;
}
export interface CommandHandler<C extends Command, R = void> {
execute(command: C): Promise<R>;
}
export interface CommandBus {
dispatch<C extends Command, R = void>(command: C): Promise<R>;
register<C extends Command, R = void>(commandClass: { TYPE: string }, handler: CommandHandler<C, R>): void;
}

✅ You can build your own CommandBus by following this interface.


In production systems where services are distributed or deployed as independent processes, it’s often necessary to dispatch commands across process boundaries. DomusJS allows full flexibility in this area by enabling you to implement your own distributed CommandBus.

Examples of distributed patterns:

  • gRPC-based CommandBus — Send commands over the network using Protocol Buffers
  • Message Queue CommandBus — Dispatch commands via RabbitMQ, Redis, or similar
  • Hybrid architecture — Some commands locally, others remotely based on routing rules

A distributed CommandBus over gRPC might:

  1. Serialize a command into a Protobuf message
  2. Send it to a remote service over the network
  3. Deserialize and dispatch to the appropriate handler on the remote side

This pattern is particularly useful for:

  • Microservices that need to communicate across service boundaries
  • Bounded contexts that operate as independent processes
  • Multi-tenant systems where commands route to different instances