PseudoCQRS,用于开发 MVC 应用程序的框架






4.85/5 (21投票s)
本文概述了 PseudoCQRS 开源项目,这是一个用于开发 MVC Web 应用程序的程序包。
PseudoCQRS 简介
命令查询职责分离 (CQRS) 是 Greg Young (http://codebetter.com/gregyoung/) 最初描述的一个模式。您可以在此处 https://martinfowler.com.cn/bliki/CQRS.html 和此处 http://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf 阅读 CQRS 模式。
CQRS 模式的一个关键概念是,应用程序的状态是根据系统中发生的事件确定的。这与“标准”N 层应用程序不同,在标准 N 层应用程序中,应用程序的状态存储在数据存储中。
在标准 N 层应用程序中,如果您想创建一个客户,您会创建一个 Customer 对象,然后通过某种存储库(可能通过 NHibernate 等 ORM)将其保存到数据库。然后,如果您要更新地址,您将从存储库中检索客户,更新它,然后再次通过存储库的 Save 方法将其保存。
使用 CQRS,客户对象的状态保存在内存中,您持久化到数据存储的是影响该客户的实际事件。由于您记录了所有这些事件,如果您关闭应用程序然后重新启动,您只需遍历所有事件即可恢复 Customer 对象以及系统中其他部分的状态。
我们创建 PseudoCQRS 的原因是,我们想将 CQRS 模式应用于现有应用程序——一个已经将所有状态信息存储在数据库中的应用程序。无法重构该应用程序以使用“纯粹”CQRS 实现,但我们希望使用应用程序的“命令”端和“读取”端之间的清晰分离。因此,PseudoCQRS 应运而生。
PsuedoCQS 是用 C# 编写的,主要面向 Asp.NET MVC 应用程序(3 或 4 版)。
PseudoCQRS 架构
根据 CQRS 模式,PseudoCQRS 架构有两端——“命令”端和“查询”端。这种分离如下图所示。
从图中可以看出,PseudoCQRS 分为两端:“查询”端和“命令”端。
这与 CQRS 模式一致,该模式描述了用户在您的应用程序中执行的两个主要操作——他们查看应用程序中已有的数据(“查询”端)以及他们执行会影响该数据的命令(“命令”端)。
查询端
在我们编写的 Web 应用程序中,总有一组屏幕供用户查看他们的数据状态。例如“列出所有客户”、“显示一个客户的详细信息”、“显示特定客户的财务报表”。这些屏幕很少显示有关某个实体内的所有数据,并且仅仅显示所有数据。例如,“列出所有客户”屏幕,我们只需要显示他们的名字、姓氏,也许他们的电子邮件地址和电话号码。我们不需要显示他们的完整邮寄地址、客户的备注字段、他们的出生日期等——我们只需要一部分数据。
同样,在“显示一个客户的详细信息”页面上,我们不仅需要 Customer 实体的所有数据,还可能需要其他实体的一些数据——他们居住的国家,或者最先接触该客户的销售人员的姓名,或者与我们最近的互动的所有备注。所以,我们不能只从 Customer 实体获取数据——我们需要额外的数据。
在几乎所有屏幕中都是这种情况——我们需要一些“自定义”数据才能显示该屏幕。
这在 MVC 应用程序中不是一个新概念——实际上,这就是 MVC 的“M”——“模型”,或者更常见的称呼是“ViewModel”。
这个 ViewModel 将特定于某个 View(即屏幕),并且可能不会在任何其他屏幕中重用(一个小的例外是 ViewModel 的某些部分可能跨屏幕重用——例如,AddressViewModel 或类似的),因此在 PseudoCQRS 中,我们将其扩展到每个 ViewModel 都有一个单独的“ViewModelProvider”,负责创建该 ViewModel。
ViewModelProvider 使用自定义 SQL 语句和 ORM 查询的组合来检索 ViewModel 所需的所有数据,并构建该 ViewModel 并返回它。事实上,“纯粹”CQRS 更进一步,认为您可能为每个屏幕都有一个扁平化的表,当执行命令时进行填充,然后在查询端可以直接读取,从而使数据库查询速度极快。我们没有走那么远,但我们为每个需要的 ViewModel 创建了自定义查询。我们通常使用一个名为“SqlObjectHydrator”的开源项目来快速轻松地将这些查询转换为我们的 ViewModel,但我们也有一些将 ORM 查询塑造到 ViewModel 中的情况。构建 ViewModel 的方法并不重要——重要的是每个 View 都有一个 ViewModelProvider,它构建一个 ViewModel,其中包含该 View 所需的所有数据。
这种模式使我们能够非常具体地专注于编写我们所需的内容,以便显示我们所需的数据。我们不必担心重用视图对象的一部分,我们发现这很快就会变得非常混乱——取而代之的是,每个 View、ViewModel 和 ViewModelProvider 都有一个单一的关注点,并且能够很好、很快地完成该单一关注点。
一旦 ViewModel 被 ViewModelProvider 构建完成,它就会被传递给将显示它的控制器。我们创建了三个基础控制器供我们使用——BaseReadController、BaseReadExecuteController 和 BaseExecuteController。这些基础控制器中的每一个只有一个操作——一个“Execute”操作。ReadControllers 仅显示视图(例如,“列出客户”),ExecuteControllers 仅执行命令(例如,“删除客户”),ReadExecuteControllers 则兼顾两者(例如,“显示客户进行编辑,然后执行处理该编辑的命令”)。
使用我们创建的基础控制器并没有真正要求,但我们发现这有助于更好地对齐功能的垂直切片——一个控制器、一个视图、一个 viewmodel、一个 viewmodelprovider。
命令端
除了查看数据状态外,用户还需要使用应用程序做的另一件事就是更改该状态。这是通过 PseudoCQRS 的命令端来完成的。
我们仍然使用我们的基础控制器来完成这项工作——我们的 BaseExecuteController 用于仅执行命令(“删除此客户”)以及 BaseReadExecuteController 用于需要先显示一些数据然后执行命令(“更新此客户的详细信息”)的情况。
Execute 类型控制器接受一个 ViewModel,尝试验证该 ViewModel(通过标准的 MVC 验证属性,或您喜欢的任何其他验证框架——我们使用 FluentValidation),如果验证成功,则将 ViewModel 映射到 Command,然后将该 Command 放入 CommandBus 以供执行。
CommandBus 接收命令,首先运行必要的任何验证规则以验证命令。这些验证规则是业务逻辑类型的规则——例如“如果客户有待处理订单,则不允许删除客户”或“只允许特定用户删除客户”)。CommandBus 对命令运行这些验证规则,如果所有验证规则都通过,它会查找特定命令的 CommandHandler。
如果它找到了命令的 CommandHandler(即实现了 ICommandHandler<T> 的东西,其中 T 是命令的类型),它会将命令传递给该处理程序以供执行。
CommandHandler 然后处理该命令。这通常涉及一些 ORM 查询。例如,UpdateCustomerCommand 将有一个 UpdateCustomerCommandHandler,它需要通过 ORM 从数据库中提取现有客户,更新 Customer 实体上需要的任何属性,然后通过 ORM 将该 Customer 保存回数据库。
CommandHandler 还可能做的另一件事是向 EventPublisher 发布“事件”。例如,如果一个客户被更新,UpdateCustomerCommandHandler 可能会发布一个“CustomerUpdatedEvent”,其中包含被更新客户的详细信息以及所做的更新。然后,对象可以订阅这些事件的通知(通过实现 IEventSubscriber<T> 接口,其中 T 是事件的类型)。EventSubscriber 可以做一些事情,例如向管理员发送电子邮件告知他们更改,或将更改的历史记录记录到数据库,或在客户下订单时向客户发送 SMS 等。他们甚至可以做一些事情,例如为应用程序的读取端创建“扁平化”数据,以便该端可以更高效。例如,如果一个客户创建了一个新订单,并且在客户列表屏幕上您始终显示每个客户的未结余额,那么您可以使用 EventSubscriber 来计算该未结余额,并在发生相关事件(OrderPlacedEvent 或 PaymentMadeEvent)时将其“缓存”到 Customer 表中。这样,当您的 ViewModelProvider 需要获取该数据时,它在显示列表时就不必不断地重新计算客户的余额——它只需要从未结余额缓存中获取数据。
结论
我们编写 PseudoCQRS 是为了让我们能够利用 CQRS 模式提供的非常清晰的关注点分离,同时能够为遗留应用程序重用现有的数据结构(以及大部分现有代码)。此后,我们也使用它编写了一个全新的应用程序,并且它对我们来说效果很好。
本文是架构的摘要。下一篇描述 PseudoCQRS 查询端的文章可以在此处找到
PseudoCQRS 是一个开源项目,可以在 GitHub 上下载代码,地址:https://github.com/LiquidThinking/PseudoCQRS
源代码中包含了一个示例应用程序,该应用程序实现了 Scott Hansleman 创建的 NerdDinner 示例的“创建晚餐”功能,用于演示 Asp.NET MVC 的功能。
该程序包也可用在 NuGet 上(包名称为 PseudoCQRS 和 PseudoCQRS.Mvc4)
如何使用 PseudoCQRS
- 创建一个空的 asp.net mvc Web 应用程序项目
- 通过 Visual Studio 的程序包管理器控制台安装 PseudoCQRS 程序包 => install package ( PseudoCQRS | PseudoCQRS.Mvc4 ) for mvc framework 3 or 4。
Pseudo CQRS 除了某些标准系统程序集和 System.Web.MVC.dll(版本 3 或 4)外,没有外部依赖项。