命令查询职责分离 (CQRS)
2011年7月14日
CQRS 代表**命令查询职责分离**。这是一种我第一次从 Greg Young 那里听到的模式。其核心概念是,您可以使用与读取信息不同的模型来更新信息。对于某些情况,这种分离可能很有价值,但请注意,对于大多数系统,CQRS 会增加风险复杂性。
人们用于与信息系统交互的主流方法是将其视为 CRUD 数据存储。我的意思是,我们对某些记录结构有一个心理模型,我们可以**创建 (c)** 新记录,**读取 (r)** 记录,**更新 (u)** 现有记录,以及在使用完记录后**删除 (d)** 记录。在最简单的情况下,我们的交互都是关于存储和检索这些记录。
随着我们的需求变得越来越复杂,我们逐渐摆脱了这种模式。我们可能希望以不同于记录存储的方式查看信息,也许将多条记录合并为一条记录,或者通过组合不同位置的信息来形成虚拟记录。在更新方面,我们可能会发现验证规则,这些规则只允许存储某些数据组合,甚至可以推断出要存储的数据与我们提供的数据不同。
当这种情况发生时,我们开始看到信息的多种表示形式。当用户与信息交互时,他们会使用这些信息的各种表示形式,每种表示形式都是不同的。开发人员通常会构建自己的概念模型,用于操作模型的核心元素。如果您正在使用领域模型,那么这通常是领域的概模型念表示。您通常还会使持久存储尽可能接近概念模型。
这种多层表示的结构可能会变得相当复杂,但是当人们这样做的时候,他们仍然会将其解析为一个单一的概念表示,这个概念表示充当所有表示之间的概念集成点。
CQRS 引入的变化是将概念模型拆分为用于更新和显示的单独模型,根据 命令查询分离 的词汇表,分别称为命令和查询。其基本原理是,对于许多问题,尤其是在更复杂的领域中,对命令和查询使用相同的概念模型会导致模型更加复杂,而这两种模型都无法很好地完成任务。
通过单独的模型,我们通常指的是不同的对象模型,它们可能在不同的逻辑进程中运行,甚至可能在不同的硬件上运行。一个 Web 示例将看到用户正在查看使用查询模型呈现的网页。如果他们启动了一个更改,该更改将被路由到单独的命令模型进行处理,结果更改将被传送到查询模型以呈现更新后的状态。
这里有很大的变化空间。内存模型可以共享同一个数据库,在这种情况下,数据库充当两个模型之间的通信桥梁。但是,它们也可以使用单独的数据库,有效地将查询端的数据库转换为实时 报表数据库。在这种情况下,两个模型或它们的数据库之间需要有一些通信机制。
这两个模型可能不是独立的对象模型,可能是同一个对象在其命令端和查询端具有不同的接口,就像关系数据库中的视图一样。但是通常当我听到 CQRS 的时候,它们显然是独立的模型。
CQRS 自然适合其他一些架构模式。
- 当我们不再使用通过 CRUD 交互的单一表示形式时,我们可以轻松地转向基于任务的用户界面。
- CQRS 非常适合 基于事件的编程模型。通常会看到 CQRS 系统被拆分为通过 事件协作 进行通信的独立服务。这使得这些服务可以轻松地利用 事件溯源。
- 拥有单独的模型会引发关于如何保持这些模型一致性的问题,这增加了使用 最终一致性 的可能性。
- 对于许多领域,在更新时需要大部分逻辑,因此使用 预先读取派生 来简化查询端模型可能是有意义的。
- 如果写入模型为所有更新生成事件,则可以将读取模型构造为 事件发布者,从而使其成为 内存映像,从而避免大量数据库交互。
- CQRS 适用于复杂的领域,这类领域也受益于 领域驱动设计。
何时使用它
与任何模式一样,CQRS 在某些地方有用,但在其他地方则不然。许多系统确实符合 CRUD 的心理模型,因此应该以这种风格来完成。CQRS 对所有相关人员来说都是一个重大的思维飞跃,因此除非收益值得,否则不应该轻易尝试。虽然我遇到过 CQRS 的成功案例,但到目前为止,我遇到的大多数案例都不太好,CQRS 被视为导致软件系统陷入严重困境的重要因素。
特别是,CQRS 应该只用于系统的特定部分(DDD 术语中的 限界上下文),而不是整个系统。按照这种思路,每个限界上下文都需要自己决定如何建模。
到目前为止,我看到了两个方向的好处。首先,一些复杂的领域可能更容易通过使用 CQRS 来处理。然而,我必须强调,这种适合 CQRS 的情况非常少见。通常,命令端和查询端之间有足够的重叠,共享模型更容易。在不适合 CQRS 的领域中使用 CQRS 会增加复杂性,从而降低生产力并增加风险。
另一个主要好处是处理高性能应用程序。CQRS 允许您将负载从读取和写入中分离出来,从而可以独立地扩展每个负载。如果您的应用程序在读取和写入之间存在很大差异,那么这将非常方便。即使没有这种差异,您也可以对这两方面应用不同的优化策略。例如,对读取和更新使用不同的数据库访问技术。
如果您的领域不适合 CQRS,但您有苛刻的查询会增加复杂性或性能问题,请记住,您仍然可以使用 报表数据库。CQRS 对所有查询使用单独的模型。使用报表数据库时,您仍然可以将主系统用于大多数查询,但将要求更高的查询卸载到报表数据库。
尽管有这些好处,**您在使用 CQRS 时应该非常谨慎**。许多信息系统都非常适合以与读取相同的方式更新信息库的概念,向这样的系统添加 CQRS 会增加很大的复杂性。我当然见过这样的案例,即使在有能力的团队手中,它也会严重拖累生产力,给项目带来不必要的风险。因此,虽然 CQRS 是一种很好的工具箱模式,但要注意,它很难用好,如果处理不当,很容易就会砍掉重要的部分。
进一步阅读
- Greg Young 是我听到的第一个谈论这种方法的人 - 这是 我最喜欢的他的总结。
- Udi Dahan 是 CQRS 的另一位倡导者,他对此技术进行了 详细的描述。
- 有一个 活跃的邮件列表 来讨论这种方法。