关于typescript:NestJs CQRS-与共存的TypeORM实体进行汇总设置

NestJs CQRS - aggregate setup with co existing TypeORM entities

因此这里有一个官方示例

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@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();
  }
}

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

1
2
3
4
5
6
7
8
9
10
11
export class User extends AggregateRoot {
  constructor(private readonly id: string) {
    super();
  }

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

    this.apply(new UserCreatedEvent(user));
  }
}

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

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

传入创建的实体时,出现类似

的错误

Argument of type 'User' is not assignable to parameter of type 'AggregateRoot'. Type 'User' is missing the following properties from
type 'AggregateRoot': autoCommit, publish, commit, uncommit, and 7
more.ts(2345)

之所以有意义,是因为TypeORM实体扩展了BaseEntity,而聚合扩展了AggregateRoot。不幸的是,官方示例没有显示如何处理聚合和数据库实体。

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


您提到的此doSomething函数恰恰是您所描述的,用于发出事件。我没有看到任何其他功能,因为我们将所有业务逻辑从"模型"移到了"服务",所以是的。

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

而不是:

1
2
3
4
5
6
7
8
9
    const createdUser: User = await this.usersRepository.createUser(
      command.username,
      command.password,
    );

    const userAggregate: UserAggregate = this.eventPublisher.mergeObjectContext(
      createdUser,
    );
    userAggregate.raiseUserCreatedEvent(createdUser);

1
2
3
4
5
6
7
    const userAggregate: UserAggregate = this.eventPublisher.mergeObjectContext(
      await this.usersRepository.createUser(
        command.username,
        command.password,
      )
    );
    userAggregate.raiseUserCreatedEvent();

然后在user.model中执行:

1
2
3
public raiseUserCreatedEvent(): void {
    this.apply(new UserCreatedEvent(this));
  }

btw,如果要删除以下行中的: User类型:

1
const createdUser: User = await this.usersRepository.createUser(

它可能会起作用,因为您不必在这里设置类型,这是不必要的,typescript可以自己猜测类型,因此在这种情况下您不必自己做。


@cojack给出的答案假定您的TypeORM实体定义和CQRS AggregateRoot由同一类定义。许多NestJS CQRS项目都是这样实现的(尽管这不是最佳实践,并且违反了单一职责原则。并且您的域类不应与数据库具有依赖关系)。

正如您在注释中指出的那样,如果您的TypeORM定义必须从BaseEntity扩展,则这是有问题的,因为您也不能从AggregateRoot扩展。

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

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

有关此示例,请参见nestjs-rest-cqrs-example项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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();
  }
}

就聚合根中的事件而言,该项目并没有做太多事情,但我希望您能明白。

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


mergeObjectContext方法应接收AggregateRoot实例作为参数

1
const userAggregate = this.eventPublisher.mergeObjectContext(new User(createdUser.id);