NestJs CQRS - 与现有的 TypeORM 实体聚合设置

Que*_*n3r 8 cqrs typescript nestjs

所以这里有一个官方的例子

https://github.com/kamilmysliwiec/nest-cqrs-example

我尝试为三个简单的功能创建自己的功能:

  • 获取所有用户
  • 通过 id 获取一个用户
  • 创建用户

我正在使用 TypeORM 并且有一个基本的 User 实体。因此,基于官方示例代码,我创建了一个用于创建用户 ( create.user.handler.ts)的命令处理程序:

@CommandHandler(CreateUserCommand)
export class CreateUserHandler implements ICommandHandler<CreateUserCommand> {
  constructor(
    private readonly usersRepository: UsersRepository,
    private readonly eventPublisher: EventPublisher,
  ) {}

  public async execute(command: CreateUserCommand): Promise<void> {
    const createdUser: User = await this.usersRepository.createUser(
      command.username,
      command.password,
    );

    // const userAggregate: UserAggregate = this.eventPublisher.mergeObjectContext(
    //   createdUser,
    // );
    // userAggregate.doSomething(createdUser);
    // userAggregate.commit();
  }
}
Run Code Online (Sandbox Code Playgroud)

它所做的只是将一个新的用户实体持久化到数据库中。但是我现在没有得到的是如何处理 User 聚合。合并对象上下文时,我无法传入创建的用户实体。而且我不知道聚合应该处理哪个逻辑。因此,基于此用户聚合 ( user.model.ts):

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

  public doSomething(user: UserEntity): void {
    // do something here?

    this.apply(new UserCreatedEvent(user));
  }
}
Run Code Online (Sandbox Code Playgroud)

我知道我可以引发事件,即创建了一个新用户并将其推送到历史记录。但这是它唯一负责的事情吗?

那么我将如何设置用户聚合并正确创建用户处理程序?

当传入创建的实体时,我收到类似的错误

“User”类型的参数不能分配给“AggregateRoot”类型的参数。“用户”类型缺少“AggregateRoot”类型中的以下属性:autoCommit、publish、commit、uncommit 和 7 more.ts(2345)

这是有道理的,因为 TypeORM 实体 extendsBaseEntity并且聚合 extends AggregateRoot。不幸的是,官方示例没有展示如何处理聚合和数据库实体。

更新:删除了指向临时存储库的链接,因为它不再存在。

Arn*_*ver 5

@cojack 的回答假设您的 TypeORM 实体定义和 CQRS AggregateRoot 是由同一个类定义的。许多 NestJS + CQRS 项目都是这样实现的(虽然这不是最佳实践,并且违反了单一职责原则。并且您的域类不应该依赖于您的数据库)。

正如您在评论中所述,如果您的 TypeORM 定义必须从 扩展BaseEntity,这是有问题的,因为您也不能从AggregateRoot.

但没有要求这样做。在您的命令处理程序中,您可以调用以UsersRepository例如验证具有该名称的用户是否已存在,然后创建该用户。

然后,您mergeObjectContext可以实例化在单独的类中定义的聚合根实体,并从您刚刚创建的 User 实体传递适当的信息。

这个例子可以在nestjs-rest-cqrs-example项目中找到:

@CommandHandler(CreateAccountCommand)
export class CreateAccountCommandHandler implements ICommandHandler<CreateAccountCommand> {
  constructor(
    @InjectRepository(AccountEntity) private readonly repository: AccountRepository,
    private readonly publisher: EventPublisher,
  ) {}

  async execute(command: CreateAccountCommand): Promise<void> {
    // Validation of the command (check if account with this email already exists).
    await this.repository.findOne({ where: [{ email: command.email }]}).then((item) => {
      if (item) throw new HttpException('Conflict', HttpStatus.CONFLICT);
    });

    const { name, email, password } = command;
    // Create the account via the repository. The TypeORM entity is returned.
    const result = await this.repository.save(new CreateAccountMapper(name, email, bcrypt.hashSync(password)));

    const account: Account = this.publisher.mergeObjectContext(
      // Create the AggregateRoot entity to be passed to the context.
      new Account(result.id, name, email, result.password, result.active),
    );
    account.commit();
  }
}
Run Code Online (Sandbox Code Playgroud)

这个项目在聚合根中的事件方面做得并不多,但我希望你明白了。

有很多方法可以布置 CQRS 模型和添加逻辑的位置。有关更复杂但布局合理的示例,请参见Adrian Lopez 的Daruma Backend


coj*_*ack 4

你提到的这个doSomething函数,正是为了你所描述的,发出一个事件。我没有看到它的任何附加功能,因为我们将所有业务逻辑从“模型”转移到“服务”,所以是的。

回复评论,因为评论有障碍

代替:

    const createdUser: User = await this.usersRepository.createUser(
      command.username,
      command.password,
    );

    const userAggregate: UserAggregate = this.eventPublisher.mergeObjectContext(
      createdUser,
    );
    userAggregate.raiseUserCreatedEvent(createdUser);
Run Code Online (Sandbox Code Playgroud)

    const userAggregate: UserAggregate = this.eventPublisher.mergeObjectContext(
      await this.usersRepository.createUser(
        command.username,
        command.password,
      )
    );
    userAggregate.raiseUserCreatedEvent();
Run Code Online (Sandbox Code Playgroud)

然后在user.model做:

public raiseUserCreatedEvent(): void {
    this.apply(new UserCreatedEvent(this));
  }
Run Code Online (Sandbox Code Playgroud)

顺便说一句,如果您要删除: User行中的类型:

const createdUser: User = await this.usersRepository.createUser(
Run Code Online (Sandbox Code Playgroud)

它可能会起作用,因为你在这里明确设置了一个类型,这是没有必要的,打字稿可以自己猜测类型,所以在这种情况下你不必自己做。