DDD基于编排的Saga竞态条件问题:买家校验后被封禁的业务冲突
解决Saga流程中买家封禁状态竞态问题的方案
这是分布式事务场景下典型的竞态条件问题——Saga多步校验完成后,依赖的领域状态(卖家的封禁列表)发生了变更,导致最终创建的邀约不符合最新业务规则。以下是几种落地性较强的解决方案:
1. 二次校验+补偿机制
- 在Saga流程的最后一步(创建邀约前),再次调用Seller聚合根的封禁状态校验方法。虽然无法完全杜绝“校验完成瞬间被封禁”的极端场景,但能覆盖绝大多数常规情况。
- 给邀约新增
待确认状态:卖家端收到邀约时,先触发实时封禁校验——如果买家已被封禁,直接自动拒绝邀约,并触发Saga补偿流程(通知买家邀约无效);校验通过后再进入卖家手动处理环节。 - 伪代码示例:
// 创建邀约前二次校验 if (seller.checkBuyerIsBanned(buyerId)) { throw new BuyerBannedException("买家已被卖家封禁,无法发起邀约"); } // 卖家端接收邀约时的校验逻辑 public void receiveOffer(Offer offer) { if (this.isBuyerBanned(offer.getBuyerId())) { offer.reject("买家已被封禁"); notifyBuyer(offer); } else { // 进入卖家手动处理流程 } }
2. 事件驱动的状态拦截
- 给Seller聚合根的“封禁买家”动作添加事件发布:当卖家封禁某个买家时,发布
BuyerBannedBySellerEvent事件。 - 在Saga执行过程中订阅该事件,一旦收到当前流程对应买家的封禁事件,立即终止Saga,阻止邀约创建。
- 同时在邀约生命周期中监听该事件:若邀约已创建但买家被封禁,自动将邀约标记为无效并通知双方。
3. 乐观锁+版本号控制
- 在Seller聚合根中给封禁列表添加版本号,每次修改封禁列表时版本号递增。
- 第一次校验买家封禁状态时,同时获取当前版本号;创建邀约的最后一步,携带该版本号再次校验:
- 若版本号不一致,说明封禁列表已修改,重新执行校验逻辑;
- 若版本号一致且买家未被封禁,允许创建邀约。
- 伪代码示例:
// 第一次校验时获取版本号 BanListVersion version = seller.getBanListVersion(); boolean isBanned = seller.checkBuyerIsBanned(buyerId); // 后续流程... // 创建邀约前的一致性校验 if (seller.getBanListVersion() != version || seller.checkBuyerIsBanned(buyerId)) { throw new InvalidOfferException("买家状态已变更,无法发起邀约"); }
4. 数据层约束兜底
- 如果使用关系型数据库,可在邀约表插入时通过SQL逻辑做实时校验:只有当买家不在卖家封禁列表中时,才允许插入邀约记录。
- 示例SQL:
INSERT INTO offers (buyer_id, seller_id, product_id, status) SELECT ?, ?, ?, 'PENDING' WHERE NOT EXISTS ( SELECT 1 FROM seller_bans WHERE seller_id = ? AND buyer_id = ? ); - 若插入行数为0,说明买家已被封禁,触发Saga失败流程。
内容的提问来源于stack exchange,提问作者manfrom
相关产品推荐
相关产品推荐

