中间人模式.md 8.7 KB

小试牛刀 -- .NET Core 中的 CQRS 与 Mediator

作为一名技术架构师,我一直在阅读并努力实践设计模式以及建筑原理。而 martinfowler.com 是我经常访问的网站之一。说实话,我是 Martin Fowler 的忠实粉丝:)。当我第一次在 martinfowler 页面上读到 CQRS 模式时,7-8 年前。我的第一印象是非常消极的 - 因为我有一种感觉,CQRS 使应用程序变得过于复杂,而不是他们需要的。但自从微服务架构和 DDD(Domain Driven Design,领域驱动设计)越来越流行后,我改变了想法。到目前为止,我已经用 CQRS 做了 2 个项目。

CQRS 概述

CQRS 是 Command Query Responsibility Segregation 的缩写。这是一种设计模式或开发实践,它允许你将创建/更新操作(我们称之为--命令)和读取(我们称之为--查询)分开,每个操作都返回它们的响应模型,以实现每个操作的清晰隔离。

🚨 但是,为什么是 CQRS?

有一个模型可以同时满足命令和查询的目的,总比一个模型满足查询和一个模型满足命令要好,不是吗? 所有的业务逻辑(包括命令和查询)都集中在一个地方,这将帮助我们减少代码行,并使我们的解决方案变得简单--简单就是最好的!即使你是初级开发人员,也可以帮助你快速熟悉源代码。即使你是一个初级开发人员,它也能帮助你快速熟悉源代码。 此外......(欢迎分享你的经验)。

是的,那是我 7-8 年前的作品。自从我第一次将这种模式应用于我的项目后,我对使用 CQRS 有了积极的评价。

请看下图:

CQRS

我有一个 Order 类,它有 2 个方法(MakeOrder 和 GetOrderById)。我还有一个 OrderModel 类,它有 2 个方法(MakeOrder 和 GetOrderById)共享--ProductName 和 OrderPersonName 只用于 GetOrderById。如果我们的业务不是增长型的,我的代码是没有问题的。但是,让我们想象一下,当你的业务逻辑是增长的时候,Order 类将不得不添加越来越多的函数来适应变化,伴随着这一点,你的 OrderModel 将变得非常复杂--我不想说我们无法控制,因为我们必须添加更多的适合新函数的属性。

🚨 那么,CQRS 能帮上什么忙呢?

首先,将命令和查询分开,可以让输入和输出模型更加专注于它们所执行的具体任务。这也使得对模型的测试更加简单,因为模型的泛化程度较低,因此不会因为额外的代码而变得臃肿。

回到上面的例子,命令输入模型应该只包含需要存储相关表(Product、Identity)的 Order 和外键 Id 的表字段,而且响应会非常小(布尔或整数)。此外,命令输入模型可能会对属性执行一些业务逻辑,以验证对象。另一方面,查询,通常会需要较小的输入模型(Id,或搜索标准),但有一个较大的响应模型,因为它们可能需要从许多源/表中获取数据。相比之下,用于查询的模型一般会包含较少的业务逻辑。

🚨 让我们尝试重构上面的示例,以应用 CQRS!

CQRS

以下是解决方案的结构:

CQRS

首先,我们应该创建 2 个接口 IMakeOrderCommandHandlerIGetOrderByIdQueryHandler

CQRS

并且,在 MakeOrderCommandHandlerGetOrderByIdQueryHandler 中实现这些接口。

CQRS

创建相应的请求和响应模型

CQRS

创建 OrderController

CQRS

等等,请记得在 Startup.cs 中注册你的接口:

CQRS

注:如果你不熟悉 ASP.NET Core 中的依赖注入。这篇文章,你应该阅读:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1

🚨 好吧,有很多文件/类需要被创建...

与任何模式一样,有优点也有缺点需要考虑。有些人可能会觉得,由于必须管理不同的模型而增加的复杂性可能超过了将它们分开的好处。此外,与所有模式一样,这个概念可能会走得太远,并开始成为生产力和代码可读性的负担。因此,使用 CQRS 模式的程度应该由每个用例来决定。如果它不能提供价值,那就不要使用它!

但是,你还是想使用 CQRS,不用担心管理不同的模型....,有没有什么设计模式或库可以帮助你?

是的,女士们,先生们!我想介绍一下 Mediatorrrr! 我想介绍一下 Mediatorrrrr!

中间人

让我们想象一下,如果机场没有空中交通管制(ATC)会发生什么。飞机 A 必须要发出信号,等待其他飞机的信号后才能降落或起飞.如果只有两三架飞机,那就没问题。但如果飞机多了,就会变得很混乱。

CQRS

A 飞机不需要向其他飞机发送和获取信号,只需要向空管中心发送信号,空管中心就会帮助向其他飞机发送信号,反之亦然。

CQRS

是的,这就是 Mediator 的设计模式。

上面示例中的空管中心为飞机提供了一个通信中心点。这和 Mediator 一样,可以帮助我们解决以下问题:

  • 减少类之间的连接数量。
  • 使用调解器接口封装对象。
  • 提供一个统一的接口来管理类之间的依赖关系。

注意:在上面的示例中,我们不需要注入 IMakeOrderCommandHandler 和 IGetOrderByIdQueryHandler,我们只需要注册 IMediator,它将帮助我们完成剩下的工作:)

通过 MediatR 库,将 Mediator 应用于 ASP.NET Core 项目变得更加容易。

让我们把 MediatR 应用到上面的样本中。

注:你可以在这里下载源码: https://github.com/thanhle0212/CQRSAndMediatorSample

CQRS

## 通过 Nuget 安装 MediatR 库

你可以通过 Nuget 包管理器使用以下命令安装 MediatR 包:

Install-Package MediatR

或者通过 .NET Core 命令行接口:

dotnet add package MediatR

我选择以下方式:

MediatR

找到 MediatR 软件包并安装:

MediatR

重构源代码,如下所示:

MediatR

更新处理程序类:

MediatR

在 Startup.cs 中注册 MediatR:

确保你从 NuGet 中安装了以下软件包"MediatR.Extensions.Microsoft.DependencyInjection"

MediatR

MediatR

更新 Order controller

我们不需要注入所有的处理程序接口,只需要注入 IMediator 接口:

MediatR

让我们用 Postman 做一些测试来验证重构性

MediatR

很好,很好用:)

单元测试

让我们写一些单元测试脚本来测试我们的 OrderController。

MediatR

我将使用 "XUnit "库来编写 UnitTest 和 Moq 来进行模拟:

MediatR

MediatR

编写测试实例:

MediatR

MediatR

显式依赖原则

虽然上面的代码示例已经帮助我们解决了一些应用 CQRS 的问题,但是,它引入了一个新的问题,即隐藏 Dependency Injection 的实现(我们是通过 MediatR 完成的)。

"显式依赖原则 "是我们应该遵循的架构原则之一。你可以从微软的这篇文章中找到原则:https://docs.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#explicit-dependencies

用 Mediator 隐藏依赖关系会产生一些问题,比如:

  • 方法执行时的运行时错误。
  • 更难跟踪依赖关系。维护代码的开发人员必须了解命名惯例,请求处理程序的调用栈是怎样的等等,才能解决依赖关系。这可能会增加维护成本。

总结

我们一起完成一个应用 CQRS 和 Mediator 模式的示例项目。它们是两种独立的设计模式--请记住这一点。

希望通过这篇文章,你可以有更多的观点来考虑在你的项目中应用 CQRS 和 Mediator。 如果你不真正了解它们是什么以及它们是如何工作的,就不要试图在你的实际项目中应用 CQRS 和 Mediator 设计模式。CQRS 和 Mediator 可能适用于某些项目,而不是所有项目。

源代码:

https://github.com/thanhle0212/CQRSAndMediatorSample