Javascript 如何从NestJS CQRS中的一个传奇中失败的后续命令引发HTTP异常?

Javascript 如何从NestJS CQRS中的一个传奇中失败的后续命令引发HTTP异常?,javascript,typescript,domain-driven-design,nestjs,cqrs,Javascript,Typescript,Domain Driven Design,Nestjs,Cqrs,我使用NestJS-CQRSrecipe来管理两个实体之间的交互:User和UserProfile。该体系结构是一个API网关NestJS服务器+每个微服务(用户、用户配置文件等)的NestJS服务器 我已经通过API Gateway上的用户和用户配置文件模块使用其自己的sagas/events/commands设置了基本交互: 创建用户时,将创建用户配置文件 当用户配置文件创建失败时,先前创建的用户将被删除 详情如下: 在用户模块中,CreateUser命令引发一个UserCreated事

我使用NestJS-CQRSrecipe来管理两个实体之间的交互:User和UserProfile。该体系结构是一个API网关NestJS服务器+每个微服务(用户、用户配置文件等)的NestJS服务器

我已经通过API Gateway上的用户和用户配置文件模块使用其自己的sagas/events/commands设置了基本交互:

  • 创建用户时,将创建用户配置文件
  • 当用户配置文件创建失败时,先前创建的用户将被删除
详情如下:

在用户模块中,CreateUser命令引发一个UserCreated事件,该事件被用户saga截获,该事件将触发CreateUserProfile命令(来自UserProfile模块

如果后者失败,UserProfilesaga将引发并拦截UserProfile失败的创建事件,该事件将触发DeleteUser命令(来自用户模块)

一切正常

如果CreateUser命令失败,则I
resolve(Promise.reject(new-HttpException(error,error.status))
向最终用户指示在用户创建过程中出现了错误

我的问题是,我无法为CreateUserProfile命令复制相同的行为,因为HTTP请求承诺显然已经从第一个命令中解决了

因此,我的问题是:如果一个后续命令在saga中失败,有没有办法使一个命令失败?我知道HTTP请求与saga触发的任何后续命令完全断开,但我想知道是否有人已经在这里玩过事件或其他东西来复制这个数据流

我使用CQR的原因之一,除了为微服务之间的数据交互提供更干净的代码外,是为了能够在任何链式命令失败时回滚存储库操作,这很好。但我需要一种方法来向最终用户指示链式命令出现问题并已回滚。

UserController.ts

@Post('createUser'))
异步createUser(@Body()createUserDto:createUserDto):承诺{
const{authUser}=wait this.authService.createAuthUser(createUserDto);
//这是在CreateUserCommand中的resolve()之后执行的
返回{user:authUser,令牌:this.authService.createAccessTokenFromUser(authUser)};
}
UserService.ts

异步createAuthUser(createUserDto:createUserDto):承诺{ 回来等这辆车 .execute(新建CreateAuthUserCommand(createUserDto)) .catch(error=>{抛出新的HttpException(error,error.status);}); } CreateUserCommand.ts

异步执行(命令:CreateAuthUserCommand,resolve:(值?)=>void){ const{createUserDto}=command; const createAuthUserDto:createAuthUserDto={ 电子邮件:createUserDto.email, 密码:createUserDto.password, phoneNumber:createUserDto.phoneNumber, }; 试一试{ const user=this.publisher.mergeObjectContext( 等待这个客户 .send({cmd:'createAuthUser'},createAuthUserDto) .toPromise() .然后((dbUser:IAuthUser)=>{ const{password,passwordConfirm,…publicUser}=Object.assign(dbUser,createUserDto); 返回新的AuthUser(publicUser); }), ); notifyCreated(); commit(); 解析(用户);//{ 返回事件$ .of类型(AuthUserCreatedEvent) .烟斗( 映射(事件=>{ 常量createUserProfileDto:createUserProfileDto={ 头像:“”, firstName:event.authUser.firstName, lastName:event.authUser.lastName, 国籍:“, userId:event.authUser.id, 用户名:event.authUser.username, }; 返回新的CreateUserProfileCommand(createUserProfileDto); }), ); } CreateUserProfileCommand.ts

异步执行(命令:CreateUserProfileCommand,resolve:(值?)=>void){ const{createUserProfileDto}=命令; 试一试{ const userProfile=this.publisher.mergeObjectContext( 等待这个客户 .send({cmd:'createUserProfile'},createUserProfileDto) .toPromise() .然后((dbUserProfile:IUserProfile)=>newuserprofile(dbUserProfile)), ); notifyCreated(); commit(); 解析(userProfile); }捕获(错误){ const userProfile=this.publisher.mergeObjectContext(新的userProfile({id:createUserProfileDto.userId}作为IUserProfile)); userProfile.notifyFailedToCreate(); commit(); resolve(Promise.reject(newhttpexception(error,500)).catch(()=>{})); } } UserProfileSagas.ts

userProfileFailedToCreate=(event$:EventObservable):Observable=>{
返回事件$
.of类型(UserProfiledFailedToCreateEvent)
.烟斗(
映射(事件=>{
返回新的DeleteAuthUserCommand(event.userProfile);
}),
);
}
DeleteUserCommand.ts

异步执行(命令:DeleteAuthUserCommand,resolve:(值?)=>void){ const{deleteAuthUserDto}=命令; 试一试{ const user=this.publisher.mergeObjectContext( 等待这个客户 .send({cmd:'deleteAuthUser'},deleteAuthUserDto) .toPromise() 。然后(()=>newauthuser({}作为IAuthUser)), ); user.notifyDeleted(); commit(); 解析(用户); }捕获(错误){ 解析(Promise.reject(newhttpexception(error,error.status)).catch(()=>{})); } }
在DDD术语中,您创建的
用户
用户配置文件@Post('createUser')
async createUser(@Body() createUserDto: CreateUserDto): Promise<{user: IAuthUser, token: string}> {
  const { authUser } = await this.authService.createAuthUser(createUserDto);
  // this is executed after resolve() in CreateUserCommand
  return {user: authUser, token: this.authService.createAccessTokenFromUser(authUser)};
}
async createAuthUser(createUserDto: CreateUserDto): Promise<{authUser: IAuthUser}> {
  return await this.commandBus
    .execute(new CreateAuthUserCommand(createUserDto))
    .catch(error => { throw new HttpException(error, error.status); });
}
async execute(command: CreateAuthUserCommand, resolve: (value?) => void) {
    const { createUserDto } = command;
    const createAuthUserDto: CreateAuthUserDto = {
      email: createUserDto.email,
      password: createUserDto.password,
      phoneNumber: createUserDto.phoneNumber,
    };

    try {
      const user = this.publisher.mergeObjectContext(
        await this.client
          .send<IAuthUser>({ cmd: 'createAuthUser' }, createAuthUserDto)
          .toPromise()
          .then((dbUser: IAuthUser) => {
            const {password, passwordConfirm, ...publicUser} = Object.assign(dbUser, createUserDto);
            return new AuthUser(publicUser);
          }),
      );
      user.notifyCreated();
      user.commit();
      resolve(user); // <== This makes the HTTP request return its reponse
    } catch (error) {
      resolve(Promise.reject(error));
    }
  }
authUserCreated = (event$: EventObservable<any>): Observable<ICommand> => {
    return event$
      .ofType(AuthUserCreatedEvent)
      .pipe(
        map(event => {
          const createUserProfileDto: CreateUserProfileDto = {
            avatarUrl: '',
            firstName: event.authUser.firstName,
            lastName: event.authUser.lastName,
            nationality: '',
            userId: event.authUser.id,
            username: event.authUser.username,
          };
          return new CreateUserProfileCommand(createUserProfileDto);
        }),
      );
  }
async execute(command: CreateUserProfileCommand, resolve: (value?) => void) {
    const { createUserProfileDto } = command;

    try {
      const userProfile = this.publisher.mergeObjectContext(
        await this.client
          .send<IUserProfile>({ cmd: 'createUserProfile' }, createUserProfileDto)
          .toPromise()
          .then((dbUserProfile: IUserProfile) => new UserProfile(dbUserProfile)),
      );
      userProfile.notifyCreated();
      userProfile.commit();
      resolve(userProfile);
    } catch (error) {
      const userProfile = this.publisher.mergeObjectContext(new UserProfile({id: createUserProfileDto.userId} as IUserProfile));
      userProfile.notifyFailedToCreate();
      userProfile.commit();
      resolve(Promise.reject(new HttpException(error, 500)).catch(() => {}));
    }
  }
userProfileFailedToCreate = (event$: EventObservable<any>): Observable<ICommand> => {
    return event$
      .ofType(UserProfileFailedToCreateEvent)
      .pipe(
        map(event => {
          return new DeleteAuthUserCommand(event.userProfile);
        }),
      );
  }
async execute(command: DeleteAuthUserCommand, resolve: (value?) => void) {
    const { deleteAuthUserDto } = command;

    try {
      const user = this.publisher.mergeObjectContext(
        await this.client
          .send<IAuthUser>({ cmd: 'deleteAuthUser' }, deleteAuthUserDto)
          .toPromise()
          .then(() => new AuthUser({} as IAuthUser)),
      );
      user.notifyDeleted();
      user.commit();
      resolve(user);
    } catch (error) {
      resolve(Promise.reject(new HttpException(error, error.status)).catch(() => {}));
    }
  }