DDD集成第三方系统:如何保障Aggregate一致性?
DDD领域设计下订阅类集成功能的Aggregate设计实践
Aggregate定义了系统的一致性边界与事务边界。
现有业务场景:我方应用需集成第三方系统新增订阅类功能——用户选择启用时,需调用第三方API创建对应用户;用户可随时关闭订阅,同步调用第三方API删除对应用户。针对该类功能设计对应Aggregate时,将面临两个具体问题:
- 问题1:与第三方API的交互逻辑应当放置在什么位置?
- 核心问题 问题2:若第三方系统允许用户完全绕过我方应用,直接在其系统中删除自身账号,会导致我方Aggregate与实际状态失同步,该如何处理?
问题1解答
首先咬死一个原则:Aggregate的唯一职责是维护本地领域内的业务规则一致性,不是给你当外部系统适配器用的。
千万别傻到把第三方API调用直接写在Aggregate的enableSubscribe()/disableSubscribe()方法里,一来外部IO会把领域逻辑耦合得乱七八糟,写单元测试的时候mock接口能把你搞崩溃;二来Aggregate的事务边界一旦包含外部HTTP调用,超时、网络波动会直接把本地事务拖垮。
正确的放置位置有两种成熟方案:
- 最常用的方案:把第三方调用放到应用层,Aggregate只负责完成本地状态变更、输出
UserSubscribed/UserUnsubscribed领域事件,应用层拿到事件后调用防腐层(ACL)封装好的第三方接口完成远程操作。如果要保证本地状态和外部调用的一致性,直接用Outbox模式把领域事件和本地状态存在同一个事务里,再由后台任务异步消费事件发请求,避免分布式事务的坑。 - 如果业务流程特别简单,没有多Aggregate编排的需求,也可以把外部调用完全放到领域事件的监听器里,彻底解耦Aggregate和外部依赖。
核心问题(状态失同步)解答
首先先打破一个误区:你根本不可能做到我方Aggregate和第三方系统的状态100%实时强一致——只要用户能绕开你去第三方操作,就永远存在不一致的时间窗口。设计目标从来不是消除所有不一致,而是“早发现、快修复、把不一致对用户的影响降到最低”。
具体落地可以直接照着做:
- 先把本地Aggregate的状态存厚,别只存个“已启用/已关闭”的薄字段
除了基础的状态枚举,一定要额外存:第三方平台返回的用户唯一ID、最近一次成功同步第三方的时间戳、最近一次校验第三方状态的结果。还要把状态拆细,别把“用户主动关订阅”和“第三方把账号删了”混为一个“已关闭”状态——这俩场景后续的业务逻辑完全不一样,前者是用户主动操作,后者是异常失同步,要单独拆出个“第三方状态异常”的枚举值。 - 建三层检测网,别等用户用功能报错了才知道状态不对
- 最优先接第三方的webhook回调:只要第三方支持用户删除账号的事件推送,第一时间接入,用户在侧方删完账号,第三方主动把事件推给你,你直接更新本地Aggregate状态,这是最及时的通知,能覆盖80%以上的主动删账号场景。
- 做操作时的实时校验:用户每次用订阅功能前,先抽空调一次第三方的账号查询接口,要是发现账号不存在,立刻触发本地状态修正,同时引导用户重新启用,别等调用功能的时候才报系统错误。
- 兜底的定时批量巡检:对所有标记为“已启用”的用户,后台错峰跑批量校验,比如低峰期每天轮询一次非活跃用户,活跃用户每几小时查一次,把没走到webhook、没触发用户操作的漏网之鱼找出来。
- 修状态的时候别硬改Aggregate,走领域命令遵循业务规则
检测到第三方账号真的不存在了,别直接在数据库update个状态就完事——要给对应Aggregate发一个MarkThirdPartyAccountInvalid的领域命令,让Aggregate内部的业务规则来决定后续动作:比如要不要给用户发通知、要不要暂停扣订阅费、要不要允许用户直接重新开通,所有业务判断都收在Aggregate里,别在巡检任务或者回调接口里硬编码改状态。 - 最后加个兜底容错
就算你检测再及时,总有漏网的不一致,所以在所有调用第三方功能的入口,一定要做异常捕获:如果调用时报“用户不存在”,直接给用户返回友好提示(比如“你的订阅已失效,请重新启用”),引导用户走重新开通流程,别把系统错误直接抛给用户。
内容的提问来源于stack exchange,提问作者transporter_room_3
相关产品推荐
相关产品推荐

