DDD与CQRS
更新: 3/15/2025 字数: 0 字 时长: 0 分钟
- Command 命令
- Query 查询
- Responsibility 职责
- Segregation 分离
什么是CQRS
上面的四个单词就是CQRS
所谓CQRS,简单的来说就是将查询与命令(对系统产生永久性更改的操作)二者分离
这里,我们首先要明确的一点是,CQRS其实和DDD没有直接联系,CQRS更像是一种针对数据操作进而对程序架构做出的建议
在架构上,由于查询的请求量远大于命令的请求量,因此我们希望将查询模块与命令模块分离,对二者进行分别优化(尤其是对查询进行优化)
一种常见的误区是命令模块就是进行CUD的模块,查询模块就是进行R的模块,这样理解不完全正确。
我个人对查询和命令的区别理解是根据相应的请求来区分。
即查询模块接受前端传来的查询请求,查询模块接受前端传来的查询请求,而命令模块接受前端传来的命令模块。
职责分离
二者的职责究竟是如何分离的呢?
我们可以认为一般情况下查询模块依赖于命令模块,命令模块在接收到命令后将前端发来的数据进行查询,处理与存储,在处理完后,将数据通过依赖倒置的形式提供给查询模块,查询模块在拿到这些已经处理好的数据后完成组装或拆分成便于查询的模式,然后再一一进行再存储。
在这样的解耦之后,查询模块不再关心任何计算与在加工操作,只负责接受并整合,然后去对外提供查询的任务
作用/意义
CQRS为我们提供了一种大型的数据库架构方案。
比如我们的数据,通过一些数据库(TiDB,Cassandra等)和Oss/Minio完成数据的持久化存储,对于数据的持久化修改往往也发生在这里,这一操作往往对我们的系统有很大的影响,因此应该属于命令模块,对于我们系统中数据的命令请求都应该有这一部分完成。
在这种情况下,我们的数据库在设计时就更加倾向于如何便于存储与修改来完成,强要求ACID。
而在查询时,我们往往会使用Redis(缓存),ES(搜索)来进行优化,甚至有时会将多个来源不同的数据组合成一条数据来便于查询。
然后我们的优化点就成功的拆分成了:
命令模块如何更好的保证ACID
查询模块如何高校的查询
命令模块和查询模块如何同步
这样的三个问题。
接着再根据这三个问题去进行解决。
落地
- 使用消息中间件进行解耦 通过消息中间件的使用,当获取到命令任务时,我们一旦完成了对数据的持久化更改即可向用户返回相应,表示我们对数据的修改已经完成,同时我们还要发出去一个消息,让查询模块接受我们的消息,异步的去对查询数据进行更新 这一过程的ACID靠着数据库事务来实现,同时基于消息中间件的ACK机制与·1我们认为的对消息是否发送成功设置字段进行监听的方式,即可完成命令模块与消息模块的同步
- 同数据库分表 采用同数据库分表的形式,原本异步的操作变为了同步,但是拥有数据库事务对整个内容进行保证,同时由于采取了分表的形式,我们可以对查询表采取冗余,索引等方式来实现针对查询的优化
- 共享存储 完全同表,命令事件完成就算是查询模型更行完成,极其简单,也是我们之前不考虑时采用的方式
何时使用CQRS
就如同DDD一样,其实但凡稍微复杂一点的项目就会设计到CQRS,但是这里还是给出一个指标(其实一看指标就知道几乎不可能不用CQRS)
- 涉及复杂查询(对于命令模块的数据表来说的多联表)
- 当为了方便查询不得不对领域模型增加字段时(不然就会在仓储层进行很多无意义的copy操作)
- 需要高性能查询(使用缓存等方案)
误区
可能有部分同学认为CQRS只有在分布式的场景才会用到,其实不然
最简单的CQRS其实就是读写分离的形式,也就是将Command与Query分开进行处理,在一些极度简单的业务场景下,CQRS甚至可能只是在代码层面将接口进行区分(除了入门级程序员之外几乎遇不到这种情况,demo中的demo)
然后再就是做大点业务,我们可能会为了查询的高效而设计冗余表/redis缓存来替代联表查询,这个时候CQRS的作用就显而易见了