摘要
在之前的文章 DDD-CQRS 能解什么问题中, 阐述了什么是 CQRS. 但是并没有业务需求可以应用 CQRS. 最近需要处理一个文本增量更新的业务, 经过需求分析后, 尝试使用 CQRS 来解这个问题
问题分析
一个文本页面编辑, 对象很大, 之前是全量保存. 涉及到的网络传输对象比较大, 经常超时 OOM, 所以交互改成, 只保存修改的部分, 也就是增量更新.
之前业务中没法使用 CQRS, 在于使用 CQRS 后, 数据的维护变得异常麻烦. 比如我对一个表单进行了反复修改, 生成了 N 份历史修改数据, 获取最新数据时需要对这些历史数据进行合并, 变得异常麻烦.
这次业务能够使用在于,
拆分写, 能够有效的减少数据传输.
读写可以分离, 分别扩展
通过事件溯源, 可以恢复数据到任意编辑的版本
具体设计
系统整体采用 CQRS+Event-Sourcing 来实现
CQRS
CQRS 模式通过使用不同的接口来分离读取数据和更新数据的操作. CQRS 模式可以最大化性能, 扩展性以及安全性,
还会为系统的持续演化提供更多的弹性, 防止 Update 命令在域模型 Level 发生冲突.
文本编辑这块领域模型很薄, 没有什么领域校验与约束, 按读取数据 / 更新数据分离, 当读写压力不同时, 以后可以拆分成不同的服务, 分别扩展.
Event Sourcing(事件溯源)
a. 不保存对象的最新状态, 而是保存对象产生的所有事件
b. 通过事件溯源 (Event Sourcing,ES) 得到对象最新状态;
系统整体分为三大部分
一. command
所有数据修改命令, 更新 Command, 撤销 Command, 覆盖 Command
会持久化存储到 CommitRepository 中. 然后发出事件消息
二. event-handle
对于文本编辑这个 case, 事件处理主要是合并提交的 command event. 否则事件溯源时, 需要处理的数据更新事件太多, 耗时太长.
三. query
查询数据, 能够根据修改记录获取任意 commit 的数据.
三大部分分离, 可以部署为单个服务, 也可以解耦为多个服务, 便于扩展.
需要解决的问题
如何保证事件的有序性
CQRS 的一个典型问题就是生产端的事件顺序和消费端的事件顺序不一致, 导致数据不一致的问题. 如何去解决呢?
Command 处理部分处理所有的数据更新部分, 会生成一个全局有序的 commitid, 代表着更新的顺序. 也就是生产端的事件顺序, 但是到达我们消费端的顺序却不一定是这个顺序. 所以消费端, 事件处理完成后, 会更新消费的最新 commitid. 如果当前事件的 commitid 小于最新的 commitid, 事件遗弃.
如何保证读数据性能
event handle 部分会去合并 commit, 所以读数据不是从所有的修改数据 commit 中合并数据. 数据已经预先处理了, 所以会大大加快读取效率, 可以控制待合并的数据在 5~10commits 范围之内.
数据会丢失吗
系统分离后, 没有事务保证, 数据的完整性如何保证.
当数据更新 Command 写入成功后, 代表这条数据更新成功, 这个数据就不会丢失. 因为这些数据都已经被持久化了, 剩下的问题就是读取这些提交的 Command Commit. 我们可以通过合并这些 commit, 得到最新的完整数据. 所以即使 event-handle 部分宕机了, 仍然可以读取到最新的数据.
说明
这个案例还是没有应用框架, 调研过 https://axoniq.io/product-overview/axon , 评估目前还不是太适合用, 代码可读性不强, 带来的好处不明显. 后续再考虑是否需要引入框架.
DDD 系列
我们团队是如何落地 DDD 的(1)
可落地的 DDD 的(2)- 为什么说 MVC 工程架构已经过时
可落地的 DDD(3)- 如何利用 DDD 进行微服务的划分
可落地的 DDD(4)- 如何利用 DDD 进行微服务的划分(2)
可落地的 DDD(5)- 战术设计
如何避免写出烂的业务代码(1)- 领域对象与领域服务
如何避免写出烂的业务代码(2) DDD 整改
DDD-CQRS 能解什么问题
一次关于聚合根的激烈讨论
来源: https://www.cnblogs.com/stoneFang/p/12296573.html