Transactionnal events with NestJS & CQRS

78 Views Asked by At

I'm working on an implementation of NestJS & CQRS at work to create an audit trail. I understand the main concepts, I've already researched and read up on the subject, but I'm faced with a problem for which I haven't been able to find a solution.

Each event trigger by committing (aggregate.commit()) should save a new event to the database, but here's my problem: what if a command executes several events, how can we implement a transactional method to save all or none of them and avoid inconsistencies?

Example

Imagine a command that update a user: UpdateUserCommand. When committed, 3 events are emitted:

  • UserNameUpdated
  • UserEmailUpdated
  • UserPasswordUpdated

If the UserPasswordUpdated failed, I don't want UserNameUpdated and UserEmailUpdated to be saved.

An odd alternative?

Instead of saving each event from their respective handlers, we could inject DataSource into the command handler. From there, we can save the user and each event by using transactions. This solution works, but still makes me think I've probably missed something or doing something wrong with the CQRS pattern. Here's a simplified overview:

@CommandHandler(UpdateUserCommand)
export class UpdateUserHandler implements ICommandHandler<UpdateUserCommand> {
  constructor(
    private readonly userRepository: UserEntityRepository,
    private readonly userFactory: UserFactory,
    private readonly eventPublisher: EventPublisher,
    private readonly dataSource: DataSource,
  ) {}

  async execute({ userId, dto, metadata }: UpdateUserCommand) {
    const user = this.eventPublisher.mergeObjectContext(
      await this.userRepository.findOneById(userId),
    );

    if (dto.email) user.updateEmail(dto.email);
    ... // business logic here

    await this.dataSource.transaction(async (manager) => {
      // transform user aggregate as db entity
      const userEntity = this.userFactory.create(user); 
      await manager.getRepository(User).save(userEntity);
      await manager.getRepository(Event).save(events);
    });

    user.commit();
  }
}

Thanks in advance for your answers!

NB: For context, I'm an intermediate developer new to this model (I didn't mention event-sourcing for simplicity's sake, but I'm familiar with that concept too).

0

There are 0 best solutions below