Golang领域模型-CQRS

前言: CQRS 一词随着 DDD 一同被大众所熟悉 , 但是你有没有想过 CQRS 一词其实并非 DDD 独有 , 非 DDD 设计项目也能用 , 或许你正在用 , 但你不知道而已 。 本文介绍了一下 CQRS 是什么 , 在 DDD 中的作用 , 以及在项目中使用 CQRS 的好处 。
一、什么是CQRS?先从概念开始 ,CQRS 即 Command Query Responsibility Segregation 的缩写 , 译成中文就是“命令与查询职责分离” 。
顾名思义在我们写CRUD代码的时候 , 其实是有可分为两类操作 , 一类是 query (查询) , 一类是 command (增删改) 。
记住以下几点:

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


    推荐阅读