CQRS
CQRS
可以使用以下步骤描述最简单的CRUD应用程序的流程:
- 控制器层处理
HTTP请求
并将任务委派给服务。
在大多数情况下,没有理由让中小型应用程序更复杂。但有时它还不够,当我们的需求变得更加复杂时,
我们希望拥有可扩展的系统,并且数据流动简单。
这就是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,
]
}
}
概要
这两个CommandBus
和EventBus
的观测量
。这意味着您可以轻松订阅整个流,并通过Event Sourcing
丰富您的应用程序。