CQRS全称Command Query Responsibility Segregation
在CQRS中,来自客户端的命令通过单独的路径抵达命令模型,而查询操作则采用不同的数据源,这样的好处在于可以优化对查询数据的获取,比如用于展现、用于接口或报告的数据。
CQRS这些年火起来了,常被人挂在嘴边提起。为什么?因为DDD提倡富模型,但从资源库查找所有需要显示的数据是困难的,特别是在需要显示来自不同聚合类型与实例的数据时。领域越复杂,这种困难程度越大。
有没有一种完全不同的方法可以将领域数据映射到界面显示中呢?答案正是CQRS。
在From CRUD to CQRS[1]文章中,作者比对了CRUD模式与CQRS模式
CRUD
我们传统使用的CRUD风格:
这就是经典的CRUD应用模式。数据流在应用中是这样的:
所有的业务服务与领域服务都在同一条数据流中完成数据的获取与更新操作。
CQRS
相对于CRUD,CQRS应用模型,会有两条数据流:读与写
写命令数据流负责创建/更新/删除领域模型
读数据流负责从数据源获取数据
CQRS风格整体大概有三种形式:
1、应用完全分割成两个部分:
2、应用有一个通用的web api层,但业务层分割成两部分:
3、webapi与business都是通用,command和query在通用服务中创建
在DDD实践指南[2]中也引入了CQRS的元素,进行command与query区分。一切都很完美。
然后,现实并非如此,徐昊老师却称CQRS是邪教
总结下来有两点原因:
1、模式滥用
CQRS是个模式,模式有适用场景,然而现在CQRS成了全能模式,尤其在实践DDD时,更是标配模式。
CQRS本身是一个对象接口设计原则,把get/find和mutable的set分离
然后自然扩张变成了服务接口设计原则
有人灵机一动,用domain model做command,用query model做查询和报表
整套系统两模型
这就是把一个限定概念无限扩大
到现在,有人说graphql做query,domain model做command
然而实际上,想解决的都是报表 特别是ad hoc报表
用一个特别复杂的方案,解决个简单的问题(还比一定解决得了),逢人就说,这是应该的方案,这不是邪教是什么
为什么说CQRS是复杂的?
Udi Dahan[3]绘制了一张图:
图中可以看到Cache,写模型也要读有cache,然后你的读模型也有cache,俩cache不一致
特别是用jpa/hibernate之类的框架,cache行为很难控制,不加cache性能不好,加了cache,俩模型不一致。
而且代码量也增加,从多个聚合取数据拼装一起的代码量多,你分成cqrs代码不多吗?多一套model,代码不多吗
cqrs作为模式,是一种分布式架构模式,一个单体里大概率就是用错了
Martin Fowler[4]也表明了这个观点:
“By separate models we most commonly mean different object models, probably running in different logical processes, perhaps on separate hardware. ”- Martin Fowler.
把repo分成xxxquery 放各种find和xxxupdate 放save,理论上也叫cqrs
底层模型还是一样 只是接口分一下,那有啥用
cqrs重点在有两套模型
如果是cqrs 我建议你用独立的查询数据库或者搜索引擎
一个最典型的cqrs,cms/blog系统,读模型,file缓存,搜索引擎;写模型,后台domain,分得干干净净
2、把view model当query model
把view model当query model,本来mvc,mvp,mvvm的问题,硬拗成cqrs
围绕domain做read model,第一选择是细化domain 调整各种聚合
必须在限定问题领域内使用
行业里说cqrs的,大部分是因为 domain model提炼不力,绕过domain直接查询数据库
何时使用
既然CQRS是种模式,就得像任何模式一样,有适用场景,也有不适用场景。
很多系统很适合CRUD模式,就应该使用CRUD,那么什么场景下适合CQRS呢?
Martin Fowler指出了两个场景
1、比较复杂的领域模型
这种场景需要强调的是,使用CQRS还是很少的场景。当读写模型比较重叠时,使用共享模式相对简单些。
不然会增加复杂性,从而减小生产力并增加风险。
2、高性能应用
CQRS可以隔离读与写负载,并独立扩容。
当读与写模型有明显区别时,会很方便。即使没有,读写也可以使用不同的优化策略。
总结
可以联想到在数据库架构时,也常使用主写从读架构。那是不是也称为CQRS呢?
我们在一个应用中,真的同时使用了这两种模型吗?其实也未必,只是某些小点,借鉴了CQRS思想。
CQRS作为模式,是一种分布式架构模式,而且是很复杂的模式。流行的CQRS不过是为了查询而绕开domain的做法,不过是因为domain提炼不到位。
正常的程序,都有读写功能,不需要分成皆然不同的两套模型,就无所谓是不是CQRS了。
这也说明了一个问题,大佬说的CQRS与普通人说的CQRS其实不是一个东西,普通人为了轻松理解,就简单理解为读写分离,并且自以为这就是全部,变成了全知全能,其实这只是个开始。
References
[1]
From CRUD to CQRS: https://antonnalivayko1.medium.com/from-crud-to-cqrs-part1-cqrs-cbeac0350043
[2]
DDD实践指南: https://www.zhuxingsheng.com/blog/ddd-tactical-practice-guide.html
[3]
Udi Dahan: https://udidahan.com/2009/12/09/clarified-cqrs/
[4]
Martin Fowler: https://martinfowler.com/bliki/CQRS.html