前言: CQRS 一词随着 DDD 一同被大众所熟悉,但是你有没有想过 CQRS 一词其实并非 DDD 独有,非 DDD 设计项目也能用,或许你正在用,但你不知道而已。本文介绍了一下 CQRS 是什么,在 DDD 中的作用,以及在项目中使用 CQRS 的好处。
一、什么是CQRS?
先从概念开始, CQRS 即 Command Query Responsibility Segregation 的缩写,译成中文就是“命令与查询职责分离”。
顾名思义在我们写CRUD代码的时候,其实是有可分为两类操作,一类是 query (查询),一类是 command (增删改)。
记住以下几点:
- 这个从分离关注点的角度来看,一个 对数 据无修改,一个则对数据有修改,二者的关注点是不同的,可以将其分离。
- 二者从代码结构上看,能够将聚合根的臃肿程度降低。
- 从性能上看,可以分别最大化读写的性能。
二、要不要用CQRS?
2.1 举例说明分层实践中的常规操作
不管你是否在进行DDD项目,其实你或多或少已经在接触或者使用 CQRS 了。
常规操作.png
上图的操作方式是不是很熟悉,相当常规的开发操作, Model 包里面建个 model ,然后在 DAO 层或者说 Repository 层中对改 model 进行增删改查操作。
一般而言这种操作是没有问题,或者说大家都是这么干的。那么我举个 栗子 ,看看大家是否又能找到这种开发中熟悉的感觉!
例如:电商项目,商品(Goods)服务中的订单操作,开始只需要实现两个方法:
商品Repository
type GoodsRepository interface {
Save(ctx context.Context, order *model.Goods) error
GetByID(ctx context.Context, id int) (order *model.Goods, error)
}
好了,开启迭代模式:
- 需要展示商品详情,但是所需字段比 model.Goods 中的字段要少。
- 需要展示 Goods 列表,列表中所需要的信息比商品详情中更少。
- 根据时间、商品种类,标签、售价、利润···等维度对商品进行筛选展示。
- 展示商品的供应商,而 供应商管理 属于另一个业务服务
- 。。。
。。。
当你迭代完成,回头看你的代码,有没有发现:
- 你的 GoodsRepository 里面居然全是查询的操作代码。
- 为了应对不同的业务数据要求,需要将 model.Goods 中的数据进行裁剪与转换,这部分繁多的代码是放在 Service 中,还是在 Repository 中?是不是犯迷糊了,想想咱们 DDD 系列的第二篇文章《六边形架构》,或许你会有所收获。
CQRS 通过单独读模型解决上面的问题
2.2 CQRS的不同模式
CQRS 中的 读写分离 可以分为两个层次:
- 代码层面的读写分离。相同存储-读写模型分离.png
该模型的数据存在相同的 DateBase 中,通过在业务代码中使用不同的分离后的读写模型,减少了繁多且又没法避免的数据转换操作。
- 存储层面的读写分离。存储分离-模型分离.png该 CQRS 模型分离的更为彻底,不仅仅分离了读写模型,底层数据存储也一并分离了。读写操作分别写入 不容 的存储中,然后通过消息机制进行数据同步。
总结:
- 并不是项目中所有的 model 都需要进行 CQRS 改造,并不是所有的 CQRS 一定要将读写数据进行分离。
- 代码层面进行的 CQRS 有利于分离关注点,让代码层次结构更清晰,容易维护。
- 存储层次的 CQRS 应用于高性能查询场景,经常见于大型项目中 MySQL 存储商品,然后用 ES 查询商品等需要快速响应的场景。