CQRS:命令查询责任分离

2020-11-11 21:49:05

CQRS代表命令查询责任分离。这是我第一次听到格雷格·杨(Greg Young)描述的一种模式。其核心是这样一个概念,即您可以使用与读取信息所用的模型不同的模型来更新信息。对于某些情况,这种分离可能很有价值,但请注意,对于大多数系统来说,CQRS增加了风险复杂性。

人们在与信息系统交互时使用的主流方法是将其视为CRUD数据存储。我的意思是,我们有某种记录结构的心理模型,在这种模型中,我们可以创建新记录、读取记录、更新现有记录,以及在处理完记录后删除记录。在最简单的情况下,我们的交互都是关于存储和检索这些记录。

随着我们的需求变得越来越复杂,我们逐渐远离这种模式。我们可能希望以与记录存储不同的方式查看信息,可能是将多条记录合并为一条记录,或者通过组合不同位置的信息来形成虚拟记录。在更新方面,我们可能会发现只允许存储某些数据组合的验证规则,甚至可以推断要存储的数据与我们提供的数据不同。

当这种情况发生时,我们开始看到信息的多种表示形式。当用户与信息交互时,他们使用该信息的各种表示形式,每种表示形式都不同。开发人员通常构建他们自己的概念模型,他们使用该模型来操作模型的核心元素。如果您使用的是域模型,则这通常是域的概念性表示。通常,您还可以使持久存储尽可能接近概念模型。

这种多层表示的结构可能会变得相当复杂,但当人们这样做时,他们仍然会将其分解为单个概念表示,该概念表示充当所有表示之间的概念集成点。

CQRS引入的更改是将概念模型拆分成单独的模型进行更新和显示,根据CommandQuerySeparation的词汇表,CQRS将其分别称为Command和Query。其基本原理是,对于许多问题,特别是在更复杂的领域中,拥有相同的命令和查询概念模型会导致更复杂的模型,两者都做不好。

我们所说的独立模型通常指的是不同的对象模型,它们可能运行在不同的逻辑进程中,也可能运行在不同的硬件上。一个Web示例可以看到,用户正在查看使用查询模型呈现的网页。如果它们发起更改,该更改将被路由到单独的命令模型进行处理,所产生的更改将被传送到查询模型以呈现更新后的状态。

这里有相当大的变化空间。内存中的模型可以共享相同的数据库,在这种情况下,数据库充当两个模型之间的通信。但是,它们也可以使用单独的数据库,从而有效地将查询端的数据库变成实时报告数据库。在这种情况下,两个模型或它们的数据库之间需要某种通信机制。

这两个模型可能不是单独的对象模型,可能是相同的对象为其命令端和查询端提供了不同的接口,这与关系数据库中的视图非常相似。但通常情况下,当我听说CQR时,它们显然是两种不同的型号。

当我们从通过CRUD与之交互的单一表示形式转移到基于任务的用户界面时,我们可以很容易地转移到基于任务的用户界面。

CQRS非常适合基于事件的编程模型。CQRS系统被分成多个独立的服务,与活动协作进行通信,这是很常见的。这使得这些服务可以轻松地利用事件采购(Event Sourcing)。

拥有独立的模型会引发关于保持这些模型一致性的难度有多大的问题,这增加了使用最终一致性的可能性。

对于许多域,重新更新时需要很多逻辑,因此使用EagerReadDerivation来简化查询端模型可能是有意义的。

如果写入模型为所有更新生成事件,则可以将读取模型构造为EventPoster,从而允许它们成为Memory Images,从而避免大量的数据库交互。

与任何模式一样,CQRS在某些地方有用,但在其他地方没有用。许多系统确实符合一个肮脏的心理模型,因此应该按照这种风格来做。CQRS对于所有相关的人来说都是一次重大的思想飞跃,所以除非好处值得一跃,否则不应该去解决。虽然我遇到过CQRS的成功使用,但到目前为止,我遇到的大多数案例都不太好,CQRS被视为让软件系统陷入严重困境的一股重要力量。

特别是,CQR应该只用于系统的特定部分(在DDD行话中是BoundedContext),而不是整个系统。按照这种思路,每个受限上下文都需要自己决定如何建模。

到目前为止,我看到了两个方向的好处。首先,使用CQRS可能更容易处理一些复杂的领域。然而,我必须强调,这种适合CQRS的情况在很大程度上是少数人的情况。通常情况下,命令端和查询端之间有足够的重叠,因此共享一个模型更容易。在与CQRS不匹配的域上使用CQRS会增加复杂性,从而降低工作效率并增加风险。

另一个主要好处是在处理高性能应用程序方面。CQRS允许您将负载与读取和写入分开,从而允许您独立地扩展每个负载。如果您的应用程序看到读取和写入之间的巨大差异,这是非常方便的。即使没有这一点,您也可以对两端应用不同的优化策略。这方面的一个例子是使用不同的数据库访问技术进行读取和更新。

如果您的域不适合CQRS,但您有增加复杂性或性能问题的苛刻查询,请记住您仍然可以使用ReportingDatabase。CQRS对所有查询使用单独的模型。对于报告数据库,您仍然使用主系统进行大多数查询,但会将要求更高的查询卸载到报告数据库。

尽管有这些好处,您在使用CQRS时应该非常谨慎。许多信息系统与信息库的概念非常契合,信息库的更新方式与其读取的方式相同,将CQR添加到这样的系统中可能会增加相当大的复杂性。我当然看到过这样的情况,它严重拖累了生产率,给项目增加了不必要的风险,即使是在一个有能力的团队手中也是如此。因此,虽然CQRS是工具箱中的一个很好的模式,但要注意它很难很好地使用,如果处理不当,很容易砍掉重要的部分。

格雷格·杨(Greg Young)是我听到的第一个谈论这种方法的人--这是我最喜欢的他的总结。

UDI Dahan是CQRS的另一位倡导者,他对该技术有详细的描述。