非常教程

Nest参考手册

方法

CQRS

可以使用以下步骤描述最简单的CRUD应用程序的流程:

  1. 控制器层处理HTTP请求并将任务委派给服务。
  2. 服务层是完成大部分业务逻辑的地方。
  3. 服务使用存储库/ DAO来更改/保留实体。
  4. 实体是我们的模型 - 只是值的容器,有setter和getter。

在大多数情况下,没有理由让中小型应用程序更复杂。但有时它还不够,当我们的需求变得更加复杂时,我们希望拥有可扩展的系统,并且数据流动简单。

这就是Nest提供轻量级CQRS模块的原因,下面将详细介绍这些组件。

命令

为了使应用程序更易于理解,每个更改都必须以命令开头。分派任何命令时,应用程序必须对其作出反应。可以从服务调度命令并在相应的命令处理程序中使用命令

英雄,game.service.ts

JS

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

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

这是一个发送的示例服务KillDragonCommand。让我们看一下命令的样子:

杀-dragon.command.ts

JS

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

CommandBus是一个命令。它将命令委托给等效的处理程序。每个Command都必须有相应的Command Handler

杀-dragon.handler.ts

JS

@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();
  }
}

现在,每个应用程序状态更改都是Command发生的结果。逻辑封装在处理程序中。如果我们想要在这里添加日志记录甚至更多,我们可以将命令保存在数据库中(例如用于诊断目的)。

为什么我们需要resolve()功能?有时我们可能希望将消息从处理程序返回到服务。此外,我们可以在execute()方法的开头调用此函数,因此应用程序将首先返回到服务并将响应返回给客户端,然后异步返回此处以处理调度的命令。

活动

由于我们在处理程序中封装了命令,因此我们阻止它们之间的交互 ​​- 应用程序结构仍然不灵活,不具有反应性。解决方案是使用事件

英雄杀,dragon.event.ts

JS

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

事件是异步的。它们由模型派遣。模型必须扩展AggregateRoot课程。

hero.model.ts

JS

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

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

apply()方法尚未调度事件,因为模型和EventPublisher类之间没有关系。如何告诉模特关于出版商?我们需要mergeObjectContext()在命令处理程序中使用publisher 方法。

杀-dragon.handler.ts

JS

@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();
  }
}

现在一切都按预期工作。请注意,我们需要commit()事件,因为它们不会立即发送。当然,对象不一定存在。我们也可以轻松地合并类型上下文:

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

而已。模型现在可以发布事件。我们必须处理它们。

每个事件都可以有很多事件处理程序。他们不必彼此了解。

英雄杀,dragon.handler.ts

JS

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

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

现在我们可以将写入逻辑移动到事件处理程序中。

传奇

这种类型的事件驱动架构提高了应用程序的反应性和可伸缩性。现在,当我们有活动时,我们可以简单地以各种方式对它们做出反应。的传奇故事是从架构上看过去的积木。

传奇是一个非常强大的功能。单传奇可以听1 .. *事件。它可以组合,合并,过滤事件流。RxJS库是神奇来源的地方。简单来说,每个saga都必须返回一个包含命令的Observable。异步调度此命令。

英雄,game.saga.ts

JS

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

我们宣布了一个规则,当任何英雄杀死龙时 - 它应该获得古代物品。然后DropAncientItemCommand由适当的处理程序调度和处理。

建立

我们要处理的最后一件事是建立整个机制。

英雄,game.module.ts

JS

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

@Module({
  imports: [CQRSModule],
  controllers: [HeroesGameController],
  providers: [
    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,
    ]);
  }
}

概要

这两个CommandBusEventBus观测量。这意味着您可以轻松订阅整个流,并通过Event Sourcing丰富您的应用程序。

Nest

Nest 是一个用于构建高效,可扩展的 Node.js 服务器端应用程序的框架

Nest目录

1.介绍
2.常见问题
3.CLI
4.方法
5.执行上下文
6.微服务
7.WEBSOCKETS
8.GRAPHQL
9.技术
10.基本内容
11.迁移指南