我使用NestJS CQRS配方来管理两个实体之间的交互:User和UserProfile。该架构是一个API网关NestJS服务器+每个微服务(User、UserProfile等)的NestJS服务器。
我已经通过API Gateway上的User和UserProfile模块用它们自己的saga/events/命令建立了基本的交互:
如果后者失败,则引发UserProfileFailedToCreate事件并由UserProfile saga拦截,该事件将触发DeleteUser命令(来自用户模块)。
一切都很好。
如果CreateUser命令失败,I解析(promise.reject(new HttpException(error,error.status))
,它向最终用户指示在用户创建过程中出了问题。
UserController.ts
@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)};
}
userService.ts
async createAuthUser(createUserDto: CreateUserDto): Promise<{authUser: IAuthUser}> {
return await this.commandBus
.execute(new CreateAuthUserCommand(createUserDto))
.catch(error => { throw new HttpException(error, error.status); });
}
CreateUserCommand.ts
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(() => {}));
}
}
userProfilesagas.ts
userProfileFailedToCreate = (event$: EventObservable<any>): Observable<ICommand> => {
return event$
.ofType(UserProfileFailedToCreateEvent)
.pipe(
map(event => {
return new DeleteAuthUserCommand(event.userProfile);
}),
);
}
DeleteUserCommand.ts
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(() => {}));
}
}
用DDD术语来说,您对user
和userprofile
的创建构成了一个跨多个微服务的业务事务--一组必须一致的业务操作/规则。
在这种情况下,在创建userprofile
之前返回数据库user
意味着您返回的数据状态不一致。这不一定是错误的,但如果您这样做,您应该在客户机中适当地处理这一点。
我看到了三种可能的方法来处理这种情况:
>
您让SAGA运行,直到它们执行指示业务事务已结束的命令,然后才为客户机解析指示成功或失败的结果(例如,在错误详细信息中,您可以报告哪些步骤成功了,哪些步骤没有成功)。因此,您还没有在CreateAuthUserCommand
中进行解析。
如果创建UserProfile
可能需要很长时间(甚至需要由主持人手动验证),那么您可能需要在CreateAuthUserCommand
中解析UserProfile
中的UserProfile
,然后让客户机订阅与UserProfile相关的事件。您需要一种机制来实现这一点,但它将客户机与正在运行的事务解耦,并且可以执行其他操作。
或者,您可以将业务事务分成两个部分,客户机为其发送单独的请求:一个创建/返回经过身份验证的User
,另一个返回创建的UserProfile
。虽然User
+UserProfile
似乎属于相同的有界上下文,但它们驻留在两个不同的微服务中的事实可能表明它们不是(在这种情况下,我认为第一个微服务实际上是用于身份验证的,另一个用于用户配置文件,它对我来说指示了不同的有界上下文)。最佳实践是让微服务实现它们自己的封装的有界上下文。
(注:回答了一个老问题,希望对其他人有所帮助)
我这里少了点什么。我做错什么了吗?
问题内容: 我在jenkins中使用了一些Windows批处理命令,其中每个命令都可能失败。为了使詹金斯工作在每个步骤上失败,这些批处理命令如下所示: 换句话说:我似乎要检查每个批处理命令是否失败,然后在必要时以错误代码1退出。有没有更有意义/更方便/更好的方法来实现这一目标? 如果我不检查每个步骤并退出,jenkins将执行以下批处理命令。 问题答案: 可悲的是,您不能激活in bash之类的标
默认情况下, 只要有任务调用失败, Gradle就会中断执行. 这可能会使调用过程更快, 但那些后面隐藏的错误就没有办法发现了. 所以你可以使用 --continue 选项在一次调用中尽可能多的发现所有问题. 采用 --continue 选项, Gralde 会调用每一个任务以及它们依赖的任务. 而不是一旦出现错误就会中断执行.所有错误信息都会在最后被列出来. 一旦某个任务执行失败,那么所有依赖于
问题内容: 我正在尝试引发异常(不使用try catch块),并且程序在引发异常后立即完成。有没有一种方法可以在我引发异常之后继续执行程序?我抛出了在另一个类中定义的InvalidEmployeeTypeException,但是我希望程序在抛出该异常后继续执行。 问题答案: 试试这个:
问题内容: 我试图按照Heroku的建议通过WhiteNoise提供静态文件。当我在开发环境中运行时,会发生以下情况: 当我在设置中将此行注释掉时,静态收集命令运行不会发生意外: 这里出了什么问题,我该如何解决?我已经尝试清空静态文件输出文件夹。它运行平稳,直到开始处理一个特定文件为止。 问题答案: 这里的问题是引用文件,该文件在预期位置不存在。 当您使用该存储后端运行时,Django会尝试重写C
我使用android studio preview beta 4,打开project it build时从https://github.com/drklo/Telegram获得Telegram源代码,并出现以下错误: 我使用最新的NDK android-ndk-r16-beta1-windows-x86。add-application.mk的第100行中有错误,它是: 所以我使用的add-appl