ngATL Conference Atlanta, GA

NestJS on 2018 ngATL Conference Atlanta, GA

NestJS 2018 ngATL

LEARN MORE

Command Query Responsibility Segregation

The flow of the simplest CRUD applications could be described using the following steps:

  1. Controllers layer handle HTTP requests and delegate tasks to the services.
  2. Services layer is the place, where the most of the business logic is being done.
  3. Services uses Repositories / DAOs to change / persist entities.
  4. Entities are our models - just containers for the values, with setters and getters.

Is it a good approach? Yes, for sure. In most cases, there's no reason to make small and medium-sized applications more complicated. But sometimes it's not enough, and when our needs becomes more sophisticated we wanna have scalable systems with straightforward data flow.

That's why Nest provides a lightweight CQRS module, which components are well-described below.

Commands

To make the application easier to understand, each change has to be preceded by Command. When any command is dispatched - the application has to react on it. Commands might be dispatched from the services and consumed in appropriate Command Handlers.

heroes-game.service.ts
JavaScript TypeScript
TypeScript

@Component()
export class HeroesGameService {
  constructor(private readonly commandBus: CommandBus) {}

  async killDragon(heroId: string, killDragonDto: KillDragonDto) {
    return await this.commandBus.execute(
      new KillDragonCommand(heroId, killDragonDto.dragonId)
    );
  }
}
TypeScript

@Component()
@Dependencies(CommandBus)
export class HeroesGameService {
  constructor(commandBus) {
    this.commandBus = commandBus;
  }

  async killDragon(heroId, killDragonDto) {
    return await this.commandBus.execute(
      new KillDragonCommand(heroId, killDragonDto.dragonId)
    );
  }
}

Here's a sample service, which dispatches KillDragonCommand. Let's see how the command looks like:

kill-dragon.command.ts
JavaScript TypeScript
TypeScript

export class KillDragonCommand implements ICommand {
  constructor(
    public readonly heroId: string,
    public readonly dragonId: string) {}
}
TypeScript

export class KillDragonCommand {
  constructor(heroId, dragonId) {
    this.heroId = heroId;
    this.dragonId = dragonId;
  }
}

The CommandBus is a commands stream. It delegates commands to the equivalent handlers. Each Command has to have corresponding Command Handler:

kill-dragon.handler.ts
JavaScript TypeScript
TypeScript

@CommandHandler(KillDragonCommand)
export class KillDragonHandler implements ICommandHandler<KillDragonCommand> {
  constructor(private readonly repository: HeroRepository) {}

  async execute(command: KillDragonCommand, resolve: (value?) => void) {
    const { heroId, dragonId } = command;
    const hero = this.repository.findOneById(+heroId);

    hero.killEnemy(dragonId);
    await this.repository.persist(hero);
    resolve();
  }
}
TypeScript

@CommandHandler(KillDragonCommand)
@Dependencies(HeroRepository)
export class KillDragonHandler {
  constructor(repository) {
    this.repository = repository;
  }

  async execute(command, resolve) {
    const { heroId, dragonId } = command;
    const hero = this.repository.findOneById(+heroId);

    hero.killEnemy(dragonId);
    await this.repository.persist(hero);
    resolve();
  }
}

Now, every application state change is a result of the Command occurrence. The logic's encapsulated in handlers. If we want we can simply add logging here or even more - we can persist our commands in the database (e.g. for the diagnostics purposes).

Why do we need resolve() function? Sometimes we might wanna return a message from handler to the service. Also, we can just call this function at the beginning of the execute() method, so the application would first turn back into the service and return a response to the client and then asynchronously come back here to process the dispatched command.

Events

Since we've encapsulated commands in the handlers, we prevent interaction between them - the application structure's still not flexible, not reactive. The solution's to use events.

hero-killed-dragon.event.ts
JavaScript TypeScript
TypeScript

export class HeroKilledDragonEvent implements IEvent {
  constructor(
    public readonly heroId: string,
    public readonly dragonId: string) {}
}
TypeScript

export class HeroKilledDragonEvent {
  constructor(heroId, dragonId) {
    this.heroId = heroId;
    this.dragonId = dragonId;
  }
}

Events are asynchronous. They're dispatched by models. Models have to extend the AggregateRoot class.

hero.model.ts
JavaScript TypeScript
TypeScript

export class Hero extends AggregateRoot {
  constructor(private readonly id: string) {
    super();
  }

  killEnemy(enemyId: string) {
    // logic
    this.apply(new HeroKilledDragonEvent(this.id, enemyId));
  }
}
TypeScript

export class Hero extends AggregateRoot {
  constructor(id) {
    super();
    this.id = id;
  }

  killEnemy(enemyId) {
    // logic
    this.apply(new HeroKilledDragonEvent(this.id, enemyId));
  }
}

The apply() method does not dispatch events yet because there's no relationship between model and the EventPublisher class. How to tell the model about the publisher? We need to use a publisher mergeObjectContext() method inside our command handler.

kill-dragon.handler.ts
JavaScript TypeScript
TypeScript

@CommandHandler(KillDragonCommand)
export class KillDragonHandler implements ICommandHandler<KillDragonCommand> {
  constructor(
    private readonly repository: HeroRepository,
    private readonly publisher: EventPublisher,
  ) {}

  async execute(command: KillDragonCommand, resolve: (value?) => void) {
    const { heroId, dragonId } = command;
    const hero = this.publisher.mergeObjectContext(
      await this.repository.findOneById(+heroId),
    );
    hero.killEnemy(dragonId);
    hero.commit();
    resolve();
  }
}
TypeScript

@CommandHandler(KillDragonCommand)
@Dependencies(HeroRepository, EventPublisher)
export class KillDragonHandler {
  constructor(repository, publisher) {
    this.repository = repository;
    this.publisher = publisher;
  }

  async execute(command, resolve) {
    const { heroId, dragonId } = command;
    const hero = this.publisher.mergeObjectContext(
      await this.repository.findOneById(+heroId),
    );
    hero.killEnemy(dragonId);
    hero.commit();
    resolve();
  }
}

Now, everything works as we expected. Notice that we need to commit() events since they're not dispatching immediately. Of course, an object doesn't have to exist already. We can easily merge type context also:

TypeScript

const HeroModel = this.publisher.mergeContext(Hero);
new HeroModel('id');

That's it. A model has an ability to publish events now. We have to handle them.

Each event can have a lot of Event Handlers. They don't have to know about each other.

hero-killed-dragon.handler.ts
JavaScript TypeScript
TypeScript

@EventsHandler(HeroKilledDragonEvent)
export class HeroKilledDragonHandler implements IEventHandler<HeroKilledDragonEvent> {
  constructor(private readonly repository: HeroRepository) {}

  handle(event: HeroKilledDragonEvent) {
    // logic
  }
}
TypeScript

@EventsHandler(HeroKilledDragonEvent)
@Dependencies(HeroRepository)
export class HeroKilledDragonHandler {
  constructor(repository) {
    this.repository = repository;
  }

  handle(event) {
    // logic
  }
}

Now we can move the write logic into the event handlers.

Sagas

This type of Event-Driven Architecture improves application reactiveness and scalability. Now, when we have events, we can simply react to them in various manners. The Sagas are the last building block from the architecture point of view.

The sagas are an incredibly powerful feature. Single saga may listen for 1..* events. It can combine, merge, filter [...] events streams. RxJS library is the place where the magic comes from. In simple words, each saga has to return an Observable which contains a command. This command is dispatched asynchronously.

heroes-game.saga.ts
JavaScript TypeScript
TypeScript

@Component()
export class HeroesGameSagas {
  dragonKilled = (events$: EventObservable<any>): Observable<ICommand> => {
    return events$.ofType(HeroKilledDragonEvent)
        .map((event) => new DropAncientItemCommand(event.heroId, fakeItemID));
  }
}
TypeScript

@Component()
export class HeroesGameSagas {
  dragonKilled = (events$) => {
    return events$.ofType(HeroKilledDragonEvent)
        .map((event) => new DropAncientItemCommand(event.heroId, fakeItemID));
  }
}

We declared a rule that when any hero kills the dragon - it should obtain the ancient item. Then the DropAncientItemCommand will be dispatched and processed by the appropriate handler.

Setup

The last thing, which we have to take care of is to set up the entire mechanism.

heroes-game.module.ts
JavaScript TypeScript
TypeScript

export const CommandHandlers = [KillDragonHandler, DropAncientItemHandler];
export const EventHandlers =  [HeroKilledDragonHandler, HeroFoundItemHandler];

@Module({
  imports: [CQRSModule],
  controllers: [HeroesGameController],
  components: [
    HeroesGameService,
    HeroesGameSagas,
    ...CommandHandlers,
    ...EventHandlers,
    HeroRepository,
  ]
})
export class HeroesGameModule implements OnModuleInit {
  constructor(
    private readonly moduleRef: ModuleRef,
    private readonly command$: CommandBus,
    private readonly event$: EventBus,
    private readonly heroesGameSagas: HeroesGameSagas) {}

  onModuleInit() {
    this.command$.setModuleRef(this.moduleRef);
    this.event$.setModuleRef(this.moduleRef);

    this.event$.register(EventHandlers);
    this.command$.register(CommandHandlers);
    this.event$.combineSagas([
        this.heroesGameSagas.dragonKilled,
    ]);
  }
}
TypeScript

export const CommandHandlers = [KillDragonHandler, DropAncientItemHandler];
export const EventHandlers =  [HeroKilledDragonHandler, HeroFoundItemHandler];

@Module({
  imports: [CQRSModule],
  controllers: [HeroesGameController],
  components: [
    HeroesGameService,
    HeroesGameSagas,
    ...CommandHandlers,
    ...EventHandlers,
    HeroRepository,
  ]
})
@Dependencies(ModuleRef, CommandBus, EventBus, HeroesGameSagas)
export class HeroesGameModule {
  constructor(moduleRef, command$, event$, heroesGameSagas) {
    this.moduleRef = moduleRef;
    this.command$ = command$;
    this.event$ = event$;
    this.heroesGameSagas = heroesGameSagas;
  }

  onModuleInit() {
    this.command$.setModuleRef(this.moduleRef);
    this.event$.setModuleRef(this.moduleRef);

    this.event$.register(EventHandlers);
    this.command$.register(CommandHandlers);
    this.event$.combineSagas([
        this.heroesGameSagas.dragonKilled,
    ]);
  }
}

Summary

Both CommandBus and EventBus are Observables. It means that you can easily subscribe to the whole stream and enrich your application with Event Sourcing.

The full source code's available here.

Sponsors

Nest is an MIT-licensed open source project. It can grow thanks to the support by these awesome people. If you'd like to join them, please read more here. Thanks!

Become a sponsor