事件驱动架构中如何确保两个不同聚合之间的数据一致性?
问题分析
在采用事件溯源的微服务架构中:
- 包含两个写服务(订单服务、产品服务)和一个读写分离的只读订单详情服务
- 订单聚合通过
productItems属性存储关联产品的聚合ID,事件经Kafka同步至只读服务构建关联视图
核心问题:产品被删除后,订单写端仍保留已失效的产品ID引用,引发写侧数据不一致。
现有思路评估
1. 双方维护关联关系
这种方案存在明显缺陷:
- 违反聚合设计的边界原则,订单与产品互相持有引用会导致循环依赖、关联数据重复存储
- 分布式场景下,删除产品时批量查找关联订单会产生性能瓶颈,还容易触发并发更新冲突
2. 关联视图+Saga执行清理
可行性较高的方案:
- 复用只读服务已有的订单-产品关联视图,通过Saga编排产品删除后的订单更新流程
- 需注意配置Saga的补偿机制,应对订单服务不可用等异常场景;同时要保证视图数据的实时性,避免遗漏关联订单
3. 容忍写端不一致,依赖读端修正
需结合业务场景判断:
- 如果业务允许订单保留已删除产品的历史ID(仅用于记录历史选购行为,不影响后续操作),且客户端始终通过只读服务获取数据,这种方案是可行的
- 读端会自动移除无效关联展示,且产品ID不复用的规则能避免后续命令执行出错,但需明确业务规则,防止后续因误解写端数据引发问题
补充可行方案
软删除产品替代物理删除
不物理删除产品聚合,而是标记其为Deleted状态:
- 订单服务在处理涉及产品的命令(如添加产品、修改数量)时,先校验产品状态,若已删除则拒绝操作或提示用户
- 既保留了历史关联的完整性,又从根源避免了写端数据不一致问题
订单聚合添加产品有效性校验
在订单服务处理产品相关命令时:
- 通过订阅产品状态变更事件维护本地产品状态缓存,避免跨服务同步调用
- 处理命令前先校验缓存中的产品有效性,从入口拦截无效产品的关联操作
事件驱动的被动修正
产品服务发布ProductDeleted事件后:
- 订单服务订阅该事件,在事件处理器中检查自身聚合是否持有该产品ID
- 若存在则发布
OrderProductRemoved事件,更新订单聚合移除无效引用 - 无需提前维护关联视图,但需注意批量处理时的性能,以及确保所有订单实例都能消费到事件
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

