【系统架构】对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 条评论
登录 后参与评论

相关文章

来自专栏前端架构

透析SOA、RPC、SOAP、REST、ICE、ESB模型发展史

最初的程序全是单机程序,没有网络,没有RPC,更没有RESTful。程序猿写的东西孤独运行在单机上。

1353
来自专栏Java架构师历程

7、重构单体为微服务

本书主要介绍如何使用微服务构建应用程序,这是本书的第七章,也是最后一章。第一章介绍了微服务架构模式,讨论了使用微服务的优点与缺点。随后的章节讨论了微服务架构的方...

1413
来自专栏JAVA技术zhai

从1.6W名面试者中收集的Java面试题精选汇总(内附知识脑图)

3068
来自专栏架构师小秘圈

有经验的程序员应该如何提升自己

工作1-5年,当我们向老板提出加薪的时候,或者跳槽去“捡”offer的时候,我们底气够吗? 敢不敢不给涨薪,就“挥一挥衣袖,不带走一个bug”?是不是提出要求后...

3395
来自专栏竹清助手

从实践的角度分析WebService两种方式SOAP和REST比较

在SOA的基础技术实现方式中WebService占据了很重要的地位,通常我们提到WebService第一想法就是SOAP消息在各种传输协议上交互。近几年REST...

1274
来自专栏小狼的世界

Fedora 12 正式发布

Fedora 12 昨天正式发布了,而且最新的 Fedora 13 已经在平行的开发中了,感觉都快跟不上 Fedora 的脚步了。

1223
来自专栏java一日一条

借助 AOP 为 Java Web 应用记录性能数据

作为开发者,应用的性能始终是我们最感兴趣的话题之一。然而,不是所有的开发者都对自己维护的应用的性能有所了解,更别说快速定位性能瓶颈并实施解决方案了。

742
来自专栏腾讯Bugly的专栏

【功能发布】Crash不好找?Bugly来支招!

自Bugly上线以来,通过各位开发者的试用和口口相传,目前Bugly已经迎来了大批量的用户,在业内的反响只能用下图来形容: ? 当然也有很多程序员哥哥在使用的过...

3045
来自专栏顶级程序员

2018年2月份GitHub上最热门的Java开源项目

源 / 开源最前线 又到了揭晓 2 月份最热门 Java 开源项目排名的时候了,在本月的名单中,出现了几个新面孔,如下载神器 proxyee-down、能为应用...

7445
来自专栏Java架构师进阶

Java程序员跳槽应该学习哪些技术?

工作1-5年,当我们向老板提出加薪的时候,或者跳槽去“捡”offer的时候,我们底气够吗?

781

扫码关注云+社区