CQRS架构窥探

CQRS简介

命令查询的责任分离Command Query Responsibility Segregation (简称CQRS) 模式是一种架构体系模式,能够使改变模型状态和查询模型状态的动作实现分离,简单理解就是读写分离。属于DDD应用领域的一个模式。提到读写分离,我们都会用到数据库的读写分离,如MySQL数据库的主备,写主,读备,主备数据的同步由MySQL数据库底层帮我们完成。CQRS在理念上与数据库读写分离有相似之处,但二者的目的完全不同,数据库读写分离的目的是容灾、分摊压力、提升性能,CQRS旨在分离读写模型,解决数据显示的复杂性问题。在DDD的应用场景中,从资源库(Repository)中查询所有需要显示的数据是困难的,特别是在需要显示来自不同聚合类型与实例的数据时,领域越复杂,这种查询困难程度越大。

命令与查询分离

Bertrand Meyer在1988年出版的《面向对象软件架构》一书中提出了“命令查询分离”的概念,Meyer说,原则上一个方法不应该既修改数据又返回数据,所以我们就有了两类方法:

  1. 查询:返回数据,但不修改数据,所以不会产生副作用;
  2. 命令:修改数据,但不返回数据。这与SOLID中的单一职责(Single Resposibility Principle)一脉相承。

不过,仍然会有一些模式逃逸于这个原则之外。比如,正如Martin Flowler所说的,从一个队列或栈弹出一个元素时,不仅改变了这个队列或栈,还返回了一个元素。

CQRS处理流程

CQRS架构窥探
将数据CRUD的CUD(新增、修改、删除)操作和R(查询)进行分离,前者称为Command,走Command bus进入Domain对模型进行操作,而查询则从另外一条路径直接查询底层数据存储,比如报表输出、数据检索等,发挥SQL、搜索引擎、缓存等底层数据存储的优点。

具体要怎么做呢?针对同一个模型,我们将那些纯粹的查询功能从命令功能中分离出来。聚和将不再有查询方法,而只有命令方法。资源库也将变成只有add()或save()方法(分别支持创建和更新操作),同时只有一个查询方法,比如fromId()。这个唯一的查询方法将聚和的身份标识作为参数,然后返回该聚合实例。资源库不能使用其他方法来查询聚和,比如对属性进行过滤等。在将所有查询方法移除后,我们将此时的模型称为命令模型(Command Model)。但是我们仍然需要向用户显示数据,为此我们将创建第二个模型,该模型专门用于优化查询,我们称之为查询模型(Query Model)。

sample

在电商系统中,商品是一个很重要的元素,商品也是一个非常复杂的体系,商品体系涉及到多级类目、卖家信息、前后台类目、各种属性、SPU、SKU等等,领域和展现形式都非常复杂。大部分的商品系统都会支持搜索功能,所以商品系统经常需要引入搜索引擎来支持搜索功能,这样由于存储结构的不同再加上领域和展现形式的复杂性,导致商品展示的实现复杂度会非常高。页面的商品展示可能需要展示不同聚和类型的信息,这时我们不但要将数据对象转换成所需的领域实体,还需要根据页面需要的信息对领域实体信息进行整合和过滤,涉及到的领域实体越多,这个过程就会愈加的复杂。这时我们可以引入CQRS来解决这个问题:
CQRS架构窥探
首先我们将应用层服务拆分为命令服务和查询服务,将创建商品、删除商品等CUD操作走应用层命令服务进入领域层,完成对应的领域模型行为后,通过资源库将数据进行持久化然后发出领域事件,资源库的实现可以放在基础设施层,根据我们系统中选择的基础设施将数据写入存储系统,示例中我们选择的是关系型数据库。接下来我们对领域事件进行处理,将数据同步至搜索引擎,这里的实现方式有很多,例如MySQL我们可以监听它的binlog等等,实现方式我们不展开细谈,无论什么样的方式,都是对领域事件处理的一种变相实现。

对于商品的搜索功能,我们构建查询模型,查询模型可以根据数据的使用方式进行构建,也可以面向页面,总之无需受限于领域模型的结构,可以根据查询需求进行灵活的构建。搜索请求经过查询服务直接进入数据访问层,同样我们可以在基础设施层实现查询服务,解耦基础设施,搜索召回的数据对象直接转换成查询模型,无需经过领域层,这样不但能够简化查询逻辑,还能提升一定的性能。

总结

CQRS 让读模型和写模型完全分离,因此可以对读操作和写操作进行优化,这样会带来性能上的提升,同时也会让代码代码更清晰、更简单,代码反映了领域模型,提升了代码的可维护性。

这一切都是关于封装、低耦合、高内聚和SRP(单一职责原则)。

不过,尽管CQRS可以让应用程序变得更健壮,但并不是说所有的应用程序都要使用CQRS。如果用户界面并不过于复杂或者我们只是需要在单个视图中处理聚和,那么引入CQRS反而会增加额外的复杂性。我们应该在必要的时候选择正确的方案。

参考文献

  • 《架构师之路月刊》
  • 《实现领域驱动设计》