【系统架构】对CQRS的基础理解

CQRS由Greg Young提出,目前在DDD领域中被广泛使用。在我看来,它甚至可以被称为是一种架构风格,可以取得与MapReduce,REST同等的地位,对软件系统的整体架构产生重要影响。

CQRS即Command Query Responsibility Seperation(命令查询职责分离),其设计思想来源于Mayer提出的CQS(Command Query Seperation)。这种命令与查询的分离方式,可以更好地控制请求者的操作。查询操作不会造成数据的修改,因而它属于一种幂等操作,可以反复地发起,而不用担心会对系统造成影响。基于这种特性,我们还可以为其提供缓存,从而改进查询的性能。命令操作则与之相反,它会直接影响系统信息的改变。

查询操作与命令操作对事务的要求也不一样。由于查询操作不会改变系统状态,因而,不会产生最终的数据不一致。从请求响应的角度来看,查询操作常常需要同步请求,实时返回结果;命令操作则不然,因为我们并不期待命令操作必须返回结果,这就可以采用fire-and-forget方式,而这种方式正是运用异步操作的前提。此外,对于大多数软件系统而言,查询操作发起的频率通常要远远高于命令操作。如上种种,都是将命令与查询进行分离的根本原因。

这就很好地阐释了我们为何需要运用CQRS模式,同时也说明了CQRS的适用场景。

只要充分理解了运用CQRS模式的意图,理解CQRS模式就变得容易了许多。下图是CQRS框架AxonFramework官方文档给出的CQRS架构图。

在这个架构图中,最核心的概念是Command、Event。以我的理解,CQRS模式的风格源头就是基于事件的异步状态机模型。抛开命令查询分离这一核心原则,这才是CQRS的基础内容。

CQRS对设计者的影响,是将领域逻辑,尤其是业务流程,皆看做是一种领域对象状态迁移的过程。这一点与REST将HTTP应用协议看做是应用状态迁移的引擎,有着异曲同工之妙。这种观点(或设计视图)引出了Command与Event的概念。Command是系统中会引起状态变化的活动,通常是一种命令语气,例如注册会议RegisterToConference。至于Event,则描述了某种事件的发生,通常是命令的结果(但并不一定是直接结果,但源头一定是因为发送了命令),例如OrderConfirmed。

我发现,这种事件更接近于一种事实,即某次数据改变的结果,是一种确定无疑已经发生的事实。这一思想直接引入了Event Source,并带来Audit(审计)的好处。而它更是与Datomic数据库的设计哲学一脉相承。Datomic的设计哲学就是:“将数据(Data)看做是事实(Fact)。每个事实都是过去的痕迹,虽然这种过去可以遗忘,但却无法改变。”Event Source可以将这些事件的发生过程记录下来,使得我们可以追溯业务流程。

Command和Event都有对应的Handler来处理。它们具有一个共同的特征,即支持异步处理方式。这也是为何在架构中需要引入Command Bus和Event Bus的原因。

在UI端执行命令请求,事实上就是将命令(注意,这是一个命令对象,你完全可以将其理解为Command模式的运用。注意,命令的命名一定要恰如其分地体现业务的意图)发送到Command Bus中。Command Bus更像是一个调停者(Mediator),在接收到Command时,会将其路由到准确的CommandHandler,由CommandHandler来处理该命令。在Axon Framework中,Command Bus提供了dispatch()方法对命令进行分发。也就是说,在它的实现中,并没有对Command提供异步处理,而仅仅是完成路由的功能。当然,我们未必要拘泥与这种设计思路。例如,我们可以将Command Bus看做是消息通道,而将Command Handler看做是该消息通道的侦听者。因此,可以引入队列来实现Command Bus。

Event的处理与之相似。Axon Framework同时支持同步和异步方式。从框架角度讲,提供更多的选择是一件好事。但基于CQRS模式的核心思想来看,如果对Command(包括Event)的处理未采用异步模型,它就没有发挥出足够的优势,此时采用CQRS,反而会增加设计难度,有些得不偿失。

在Command端,基本的处理流程是由UI发起命令请求,发送到CommandBus,并由它分发给对应的Command Handler来处理命令。Command Handler会与领域对象,特别是与Aggregation Root对象通信。在处理了相关的业务逻辑后,会触发Event。一方面,它会将Event放到Event Store中;另一方面,同时会将Event发送到Event Bus,再由Event Handler处理事件。根据Axon Framework的官方文档,Event Handler会负责更新数据源,从而保证查询端能够得到最新的数据。

然而,这一过程未必能这样简单。因为整个过程可能体现的是一个状态机。Command会导致状态的迁移,并在执行Aggregate的逻辑时,触发对应的Event。Event Handler在处理事件时,并不一定是这个业务过程的终点,它可能会发送引起下一个状态迁移的命令,从而形成一个不断迁移的过程,直至业务完全结束。这就需要我们在引入CQRS时,需要改变之前的设计思路,尽量从状态迁移的角度去理解业务逻辑。UML中的状态图是一个很好的分析工具。

它也带来一个挑战,就是事务。因为整个过程都涉及到数据状态的变化,当某个状态迁移出现问题时,要保证数据的最终结果是一致的。Axon Framework的解决方案是引入Unit of Work模式。此外,在真正实现时,究竟是由Event Handler去更新数据源,还是交由Aggregate去完成,还有待考量。我倾向于由Aggregate委派给Repository来完成。从职责分配的角度来看,这种方式更为合理。因为与数据源打交道的逻辑绝对不能太过于分散,以免数据源的改变影响到整个领域层。在DDD中,持久逻辑都是被封装到Repository(在其内部,又会委派给基础设施层中提供数据访问的对象)。换言之,这种实践是符合DDD的设计思想的。

原文发布于微信公众号 - 逸言(YiYan_OneWord)

原文发表时间:2014-08-19

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏吉浦迅科技

DAY 75:阅读Configuration Options

我们正带领大家开始阅读英文的《CUDA C Programming Guide》,今天是第75天,我们正在讲解CUDA 动态并行,希望在接下来的25天里,您可以...

922
来自专栏Java编程技术

MongoDB和MySQL对比(译)

几十年来,关系型数据库已经成为企业应用程序的基础,自从MySQL在1995年发布以来,它已经成为一种受欢迎并且廉价的选择。然而随着近年来数据量和数据的不断激增,...

971
来自专栏张善友的专栏

WinRT:WebAuthenticationBroker For OAuth认证

之前开发过QQ互联的OAuth 的.NET/Mono/Windows Phone SDK,具体可以 QQ互联OAuth2.0 .NET SDK 发布以及网站QQ...

1955
来自专栏无题

分布式事务通用解决方案

先上结论, 再分别介绍分布式事务的各种实现方式. • 如果业务场景需要强一致性, 那么尽量避免将它们放在不同服务中, 也就是尽量使用本地事务, 避免使用强一致性...

3946
来自专栏智能大石头

XCode v9.6.2017.0830

新生命团队基础框架X组件,包括网络、数据库、安全、多线程、反射、序列化、模版引擎、服务代理、远程过程调用等模块,包括Mvc后台魔方、超级码神工具、消息队列等子系...

1370
来自专栏CSDN技术头条

分析型数据仓库中读写分离的实现

和以 MySQL 为代表的传统事务型数据库相比,数据仓库有一个很大的特点,就是主要面向批量写和查询进行优化,可以不支持更新、事务这些高级特性。一些商用的数据仓库...

1959
来自专栏从流域到海域

如何将结构化数据导入Solr

原文地址:https://dzone.com/articles/how-to-import-structured-data-into-solr

1572
来自专栏程序你好

微服务中使用工作流方式Sagas事务来保证数据完整

1415
来自专栏IT大咖说

618大促,苏宁如何通过citus打造分布式数据库抗住DB高负载

内容来源:2017 年 10 月 20 日,苏宁云商IT总部资深技术经理陈华军在“PostgreSQL 2017中国技术大会”进行《苏宁citus分布式数据库应...

992
来自专栏Albert陈凯

值得拥有 不容错过的Hive精华汇总

Hive作为Hadoop家族的重要一员,具有学习成本低,开发者可通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用。在攒...

1813

扫码关注云+社区