介绍
CQRS 是架构模式的缩写,代表命令查询职责分离。它将系统的动作分为命令和查询。它与CQS有关,即Command Query Separation。
本页将解释 CQRS 和 CQS。一个是如何从另一个推导出来的,CQRS 的好处及其核心原则,以及围绕该模式的一些误解。
什么是 CQS?CQS是一种设计模式,首字母缩写代表Command Query Separation。CQS 是定义系统中处理的两种类型操作的核心概念:执行任务的命令、返回信息的查询,并且永远不应该有一个函数同时完成这两项工作。
该术语是由 Bertrand Meyer 在他的“面向对象的软件构建”一书(1988 年,Prentice Hall)中创建的。他创建它是他在 Eiffel 编程语言方面的工作的一部分。
CQRS 将 CQS 的定义原则扩展到系统内的特定对象,一个是检索数据,一个是修改数据。CQRS 是更广泛的架构模式,而 CQS 是行为的一般原则。
什么是 CQRS?命令查询职责分离 (CQRS) 是系统中命令和查询的职责分离。这意味着我们正在垂直切割我们的应用程序逻辑。除此之外,我们将状态突变(命令处理)与数据检索(查询处理)分开。
CQRS由 Greg Young 定义:
命令和查询职责分离使用与 Meyer 相同的命令和查询定义,并保持它们应该是纯粹的观点。根本区别在于,在 CQRS 中,对象分为两个对象,一个包含命令,一个包含查询。
原始定义中的“对象”不连接到存储,而是连接到处理程序。我们正在为不同的业务行为创建不同的管道,而不是单独的存储。
使用 CQRS 原则构建您的架构将提供许多优势。
- 明确系统行为之间的界限:它提供了有关构建与行为相关的应用程序的具体指导。
- 更接近业务逻辑:代码和架构按其业务运营分开,从而更容易专注于业务案例。
- 松耦合:逻辑具有凝聚力且相互关联较少,从而更容易创建模块化、可维护的应用程序。
- 减少认知负荷:由于垂直拆分,相关代码保持在一起。要修改现有代码或创建新代码,您无需了解整个架构和业务逻辑。专注于特定任务更容易。
- 更容易扩展、优化和架构更改:由于我们的代码被保存在孤岛中,因此只微调一个管道更容易,其余的保持不变。更容易专注于对业务至关重要的地方的精确优化。
- 可预测性:由于隔离,您拥有操作行为的一般规则。您不会对更改应用程序状态的查询感到惊讶;保持写入和读取逻辑之间的分离可以减少最终出现“意大利面条”代码的机会。
CQRS 的核心原则是命令和查询的分离以及它们的作用。它们在系统中扮演着根本不同的角色,将它们分开意味着可以根据需要对每个角色进行优化,这对分布式系统非常有益。
Alexey Zimarev 定义了命令和查询的不同之处:
在高层次上,CQRS 声明触发状态转换的操作应该被描述为命令,并且任何超出命令执行需要的数据检索都应该被命名为查询。由于执行命令和查询的操作要求经常不同,开发人员应该考虑使用不同的持久性技术来处理命令和查询,从而将它们隔离开来。
命令、查询及其与写入和读取模型的关系定义如下。
命令命令是指令,是执行特定任务的指令。这是改变某事的意图。
命令(过程)执行某些操作但不返回结果。
命令应该传达用户的意图。处理命令应该在一个聚合上产生一个事务;基本上,每个命令都应该清楚地说明一个明确定义的更改。
命令组成写模型,写模型应尽可能贴近业务流程。
查询查询是对信息的请求。
查询(函数或属性)返回结果但不更改状态。
从特定位置获取数据或数据状态的意图。请求不应更改数据中的任何内容。由于查询不会改变任何东西,它们不需要涉及领域模型。
查询构成了 读取模型。读模型应该派生自写模型。它也不必是永久性的:可以将新的读取模型引入系统而不影响现有的读取模型。可以删除和重新创建读取模型,而不会丢失业务逻辑或信息,因为它存储在写入模型中。
CQRS 和事件溯源CQRS 由 Greg Young 于 2010 年与 Event Sourcing 大致同时引入。他将其描述为“事件溯源的垫脚石”。
事件源系统不会显式存储域对象的状态,它会存储对该状态的更改。在事件源系统中,状态存储为一系列事件。这些事件都是业务操作。读取模型是根据事件构建的,通常保存在针对查询需求调整的单独数据库中。
事件溯源与 CQRS 配合得很好,因为事件是业务运营的结果。这使其成为 CQRS 的绝佳合作伙伴,因为 CQRS 本质上将系统与业务运营分开。
了解事件溯源和 CQRS 不相互依赖很重要。你可以有一个没有 CQRS 的事件源系统,你可以在没有事件源的情况下使用 CQRS。只是两者配合得很好。
CQRS 和 DDD领域驱动设计 (DDD) 是一种优化团队对问题空间以及如何在该空间工作的理解的方法。它是关于拥有一种由业务用户和开发团队使用的无处不在的语言。在将问题概念转化为功能软件时,这种语言统一非常有用。
CQRS 和 DDD 是独立的、正交的概念。您无需使用 CQRS 即可使用 DDD,反之亦然。当它们一起使用时,DDD 可以提供有界上下文,源自业务对话,CQRS 可以定义如何在系统中移动工作。DDD 原则上与 CQRS 一致,但绝对不是相互依赖的。
CQRS 的误解1、“命令和查询需要在不同的数据库上运行,所有内容都必须存储在不同的数据库中。”
这不一定是真的。只有两者的行为和责任应该分开。这可以在代码中,在数据库结构中,或者如果需要,在不同的数据库中。不要求它们位于不同的数据库中。
对此进行扩展,CQRS 甚至不必使用数据库:它可以从 Excel 电子表格或任何其他包含数据的东西中运行。Oskar Dudycz 在一篇文章中解释了这一点:
没有什么能阻止你使用关系数据库,即使是同一张表进行读写。如果您愿意,可以继续使用 ORM。没有什么可以阻止命令添加/修改记录和查询从同一个表中检索数据。只要我们在我们的架构中将它们分开。
CQRS 适用于系统的行为,而不是数据应该存储在哪里。记住 CQRS 指的是行为,而不是存储,这是创建高效 CQRS 应用程序的关键。
2、“它只能与 DDD 一起使用,而 Event Sourcing 则需要两者”
还有一种误解,认为 CQRS 必须与 DDD 一起使用,并且两者都是 Event Sourcing 所必需的。这些不是真的;您可以在不了解 DDD 或使用其原理的情况下应用 CQRS,并且可以在没有 CQRS 或 DDD 的情况下创建事件源系统。这些概念可以很好地协同工作,但它们并不相互依赖。
3、“CQRS 会产生最终的一致性问题”
分离命令和查询并以不同方式处理它们可以使系统最终保持一致。有一种误解认为最终一致的系统由于涉及时间延迟而更加不准确,并且由于 CQRS 中命令和查询的分离,必然存在延迟,因此最终会出现一致性问题。
事实上,多种系统都会有最终的一致性问题。在接收、记录和再次调用输入之间存在延迟的任何地方。这些延迟是毫秒级的,在大多数系统的容差范围内。在实现 CQRS 时,最终一致性不再像使用其他模式时那样受到关注。
4、“CQRS 系统总是需要消息队列”
不必要。RabbitMQ 和 Kafka 等消息队列允许您根据具体情况在写入模型和读取模型之间发送消息。如果您的模型非常简单并且从不同的数据库视图开始(甚至是前面提到的 Excel 电子表格),则不需要消息队列。考虑到 CQRS 构建系统将允许您让不同类型的数据库相互交互,例如,您可以在写入模型上使用关系数据库,在读取模型上使用文档数据库。在这种情况下,消息队列将很有用,可以在写入模型发生变化时保持读取模型的更新。如果可能的话,最好使用内置的存储机制,比如事件存储的订阅或关系数据库的更改检测捕获,因为这将使您的解决方案更加直接,但这取决于特定业务上下文中的需求。