使用 Mediatr 协调查询和命令处理程序
Coordinating Query And Command Handlers with Mediatr
我正在使用 Mediatr 在 dotnet core 3.0 中实现 CQRS 模式。我有一些关于如何协调不同的多个查询和命令的问题。根据我在网上阅读的内容,这里有一些人们描述的最佳实践。
- 查询处理程序不应依赖于命令处理程序
- 命令处理程序不应依赖于查询处理程序
- 不使用装饰器,而是使用
IPipelineBehaviour
- 每个命令都应一对一映射到
HTTP Request
(例如:创建用户会将命令发送到 CreateUserCommandHandler
,这将负责所有工作)
- 对于命令处理程序,return void 。使用 Mediatr 框架,我会 return
Task<Unit>
这是我的问题,现在,我正在为 AspNet.Identity 实施 IUserStore<TUser>
,假设我们正在查看此示例
public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null) return IdentityResult.Failed(new ArgumentNullError(nameof(user)));
bool userExists = await _mediator.Send(new UserExistsQuery {UserId = user.Id}, cancellationToken);
if (!userExists) return IdentityResult.Failed(new EntityDoesNotExistError(user));
await _mediator.Send(new DeleteUserCommand {User = user}, cancellationToken);
return IdentityResult.Success;
}
在这种方法中,我基本上是使用实现用户存储来协调不同的查询和命令以删除用户。在这种情况下,我可以看到执行以下操作以使其尽可能薄并更紧密地遵循 'one command per http request'
- 摆脱
UserExistsQuery
和 UserExistsQueryQueryHandler
,将该查询移至 DeleteUserCommandHandler
并使用存储库进行查询(UserExistsQueryHandler
现在已经这样做)而不是依赖在查询处理程序上
- 而不是 returning
Task<Unit>
,return 类似于 IdentityResult
我对执行 #2 犹豫不决的原因是,感觉我正在 return 根据使用命令的上下文来执行某些操作。我正在 returning 一个 IdentityResult
只是因为我在这个实例中需要它。
此外,我之所以将它拆分成这样,首先是为了可重用性。我希望能够进行一些可以在其他地方重复使用的查询和命令。如果我 return 一个 IdentityResult
,那么这种做法就违背了目的,因为除了 UserStore<TUser>
我真的不需要它
我一直在阅读有关 IPipelineBehaviour
的内容,但它似乎更像是适用于所有 query/command 处理程序的通用解决方案(即:管道可以是 运行 每个 command/query 如果您的程序集中存在适当的类型)。但是 IPipelineBehaviour
可以用来实现自定义管道吗?在我的例子中,我会将所有逻辑移动到一个管道中,该管道只会 运行 for DeleteUserCommand
?
我已经搜索过关于这个主题的文章,但没有真正找到任何有用的东西 - 或者也许我正在搜索错误的术语。我的行话可能有误,但我可以创建仅依赖于 IMediatr
来完成删除用户的协调服务。任何反馈 and/or 阅读 material 将不胜感激。
我认为您误解了 CQRS 的概念。 DeleteAsync 操作本身就是一个命令。因此,如果您需要读取一些数据以继续您的操作,这不是查询,而只是读取操作。因此,每当您需要在命令操作中读取一些数据时,您都必须通过存储库获取它。
请注意,通过使用 CQRS,您可以将用户查询和命令的路径分开,而不是读取和写入。
在命令中,每次写入和读取都必须通过您的领域模型。因此,您的域层中可能存在一个存储库,用于从您的写入数据库中获取数据。但是对于查询,没有必要使用领域模型。
所以你的代码必须如下所示:
public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_mediator.Send(new DeleteUserCommand(){});
}
DeleteUserCommand 处理程序就像:
protected override async Task Handle(DeleteUserCommand request, CancellationToken cancellationToken)
{
if (user == null) return IdentityResult.Failed(new ArgumentNullError(nameof(user)));
bool userExists = userRepository.ExistUser(request.UserId);
if (!userExists) return IdentityResult.Failed(new EntityDoesNotExistError(user));
await userRepository.DeleteAsync(request.UserId);
return IdentityResult.Success;
}
我正在使用 Mediatr 在 dotnet core 3.0 中实现 CQRS 模式。我有一些关于如何协调不同的多个查询和命令的问题。根据我在网上阅读的内容,这里有一些人们描述的最佳实践。
- 查询处理程序不应依赖于命令处理程序
- 命令处理程序不应依赖于查询处理程序
- 不使用装饰器,而是使用
IPipelineBehaviour
- 每个命令都应一对一映射到
HTTP Request
(例如:创建用户会将命令发送到CreateUserCommandHandler
,这将负责所有工作) - 对于命令处理程序,return void 。使用 Mediatr 框架,我会 return
Task<Unit>
这是我的问题,现在,我正在为 AspNet.Identity 实施 IUserStore<TUser>
,假设我们正在查看此示例
public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (user == null) return IdentityResult.Failed(new ArgumentNullError(nameof(user)));
bool userExists = await _mediator.Send(new UserExistsQuery {UserId = user.Id}, cancellationToken);
if (!userExists) return IdentityResult.Failed(new EntityDoesNotExistError(user));
await _mediator.Send(new DeleteUserCommand {User = user}, cancellationToken);
return IdentityResult.Success;
}
在这种方法中,我基本上是使用实现用户存储来协调不同的查询和命令以删除用户。在这种情况下,我可以看到执行以下操作以使其尽可能薄并更紧密地遵循 'one command per http request'
- 摆脱
UserExistsQuery
和UserExistsQueryQueryHandler
,将该查询移至DeleteUserCommandHandler
并使用存储库进行查询(UserExistsQueryHandler
现在已经这样做)而不是依赖在查询处理程序上 - 而不是 returning
Task<Unit>
,return 类似于IdentityResult
我对执行 #2 犹豫不决的原因是,感觉我正在 return 根据使用命令的上下文来执行某些操作。我正在 returning 一个 IdentityResult
只是因为我在这个实例中需要它。
此外,我之所以将它拆分成这样,首先是为了可重用性。我希望能够进行一些可以在其他地方重复使用的查询和命令。如果我 return 一个 IdentityResult
,那么这种做法就违背了目的,因为除了 UserStore<TUser>
我一直在阅读有关 IPipelineBehaviour
的内容,但它似乎更像是适用于所有 query/command 处理程序的通用解决方案(即:管道可以是 运行 每个 command/query 如果您的程序集中存在适当的类型)。但是 IPipelineBehaviour
可以用来实现自定义管道吗?在我的例子中,我会将所有逻辑移动到一个管道中,该管道只会 运行 for DeleteUserCommand
?
我已经搜索过关于这个主题的文章,但没有真正找到任何有用的东西 - 或者也许我正在搜索错误的术语。我的行话可能有误,但我可以创建仅依赖于 IMediatr
来完成删除用户的协调服务。任何反馈 and/or 阅读 material 将不胜感激。
我认为您误解了 CQRS 的概念。 DeleteAsync 操作本身就是一个命令。因此,如果您需要读取一些数据以继续您的操作,这不是查询,而只是读取操作。因此,每当您需要在命令操作中读取一些数据时,您都必须通过存储库获取它。 请注意,通过使用 CQRS,您可以将用户查询和命令的路径分开,而不是读取和写入。 在命令中,每次写入和读取都必须通过您的领域模型。因此,您的域层中可能存在一个存储库,用于从您的写入数据库中获取数据。但是对于查询,没有必要使用领域模型。 所以你的代码必须如下所示:
public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_mediator.Send(new DeleteUserCommand(){});
}
DeleteUserCommand 处理程序就像:
protected override async Task Handle(DeleteUserCommand request, CancellationToken cancellationToken)
{
if (user == null) return IdentityResult.Failed(new ArgumentNullError(nameof(user)));
bool userExists = userRepository.ExistUser(request.UserId);
if (!userExists) return IdentityResult.Failed(new EntityDoesNotExistError(user));
await userRepository.DeleteAsync(request.UserId);
return IdentityResult.Success;
}