MediatR 库在网络上的 CQRS 示例中是否过度使用?

Tom*_*ora 14 .net c# domain-driven-design cqrs mediatr

我很难理解为什么网络上有这么多示例在解释 CQRS 模式、处理命令和查询时都使用 MediatR。

几乎在所有地方,我都看到由 MediatR 处理命令和查询的示例,但除了不需要在依赖注入容器中注册每个命令或查询之外,我没有看到它有任何好处。但随后您需要实现查询对象(继承 IRequest)、查询处理程序和查询响应对象,以便您可以在 API 控制器方法中调用_mediatr.Send(queryObject).

为什么不直接使用依赖注入将查询对象注入到 API 控制器中,您可以直接在其上调用“get”方法?喜欢:

[HttpGet]
[Route("getall")]
public async Task<IncidentQueryResult> GetAll(int page = 0, int pageSize = 25)
{
    var result = await _incidentQueries.GetIncidents(page, pageSize);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

代替:

[HttpGet]
[Route("getall")]
public async Task<IncidentQueryResult> GetAll(int page = 0, int pageSize = 25)
{
    var query = new IncidentQuery(page, pageSize);
    var result = await _mediatr.Send(query);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

然后,在该GetIncidents方法内部,对数据库进行直接 sql 调用,并将结果映射到 C# 对象。干净利落。

对我来说,MediatR 库的完美且唯一合理的使用是处理领域事件。 在实现 DDD 时,我尝试按照下面介绍的方式建立一个项目。每个矩形都是解决方案中的一个不同项目。箭头代表参考:

带有 CQRS 架构的 DDD

让我们想象一个场景:创建一个域对象需要增加存储在另一个域对象(不同的聚合)中的计数器

  1. 向 API 端点发出请求以将一些新的域对象添加到数据库(第 6 层:表示)
  2. 控制器方法使用注入到其构造函数中的命令来创建域对象(第 4 层:命令)
  3. 在命令内部,创建一个新的域对象,并存储在该对象中的“域对象创建”事件,准备在保存到数据库之前进行广播
  4. 然后命令使用基础设施层的存储库将这个新创建的对象添加到数据库中。
  5. 然后就在执行数据库保存之前:通过 MediatR 发送“域对象已创建”事件(第 2 层:基础设施)
  6. 然后该事件在第3 层:域事件处理程序之一中的应用程序中捕获。
  7. 域事件处理程序(第 3 层:应用程序)使用基础设施层的存储库来获取另一个持有要递增的计数器的域聚合,然后递增该计数器。
  8. 所有域事件均已处理,并执行保存到数据库。

所以 MediatR 对我来说仅适用于基础设施应用程序层。

人们只是为了使用 MediatR 来执行命令和查询吗?对我来说,添加命令和查询处理程序、查询和命令请求和响应类型只会添加更多没有实际价值的代码,而且只会使其更难以理解。

以下是我访问过的一些链接:

我的母语的很多网站也有这个。

在整个应用程序中使用太多处理程序会导致难以读取应用程序的功能以及触发内容。我看到的是人们在命令层处理域事件,但域可能不应该直接发送命令?

您是否需要在 CQRS 中使用 MediatR?

M H*_*her 14

MediatR只是一个解决特定需求的库。正如存储库所说:

没有依赖关系的进程内消息传递。

命令查询职责分离(或CQRS)是一种可以通过多种方式实现的设计模式。从根本上讲,只要您的读写是独立的,您就“遵循”这种模式。

所以不,您可以构建一个CQRS“兼容”的整个应用程序,而无需使用MediatR或任何其他消息传递库。也就是说,您也可以将其构建为一个大文件。CQRS只是您可以根据需要部署的众多管理代码的工具之一。

您还可以MediatR仅用于进程内消息传递,而不应用该CQRS模式。


也就是说,您可能会看到教程、博客和其他 .NETCQRS资源MediatR(或类似的消息传递库)使用的一个常见原因是,通常任何使用的应用程序CQRS也都需要以下内容:

  1. 一种在某种程度上验证查询仍然是查询并且命令仍然是命令的方法
  2. 更大程度的“关注点分离”应用于整个代码。

MediatR通常通过将命令的执行与其实现分开(可以存在于单独的项目中)来很好地解决这两个问题

例如,在您的情况下,Presentation必须了解数据库实现及其架构,以便执行查询并将它们映射到数据库资源,而不是将其作为基础设施和基础设施的关注点。根据我的经验,这可能会导致大量重复代码或项目之间出现大量意外耦合。最好让表示层全神贯注于表示并将消息发送到任何可以MediatR根据请求提供查询信息的服务(它不关心哪个服务或处理在其中注册它们)。

本质上,在您的图表中MediatR(和/或,,,,NServiceBus...如果您需要扩展到单个进程之外)将充当控制数据流并将查询/命令的客户端与其处理程序分离的方式。BrighterMassTransitRebus


所以最后来回答一下:

人们只是为了使用 MediatR 来执行命令和查询吗?

是的,也不是,他们大多将其用作单独的良好实践,与CQRS控制依赖流的模式一起很好地发挥作用。尽管您是对的,对于许多这些想法的初学者来说,他们可以以不需要或不推荐的方式将两者结合在一起。


我建议您看一下他的其他作品,Clean Code了解所有这些部分如何共同发挥作用,为未来从事该项目的开发人员创建(他所说的)“成功之坑”。他在这里有一个模板存储库,并且有一些关于它的在线讨论:https ://github.com/jasontaylordev/CleanArchitecture