You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在DDD中使用SQL维护业务不变量的方案探讨

关于DDD并发下业务不变量校验的方案分析

针对你提到的「客户订单总数不得超过3单」的并发校验问题,直接拆解各方案的优劣及更优实现:

方案一:带前置校验的原子SQL插入

核心是把校验+插入做成数据库层面的原子操作,用单条SQL实现:

INSERT INTO orders (customer_id, order_no, ...)
SELECT ?, ?, ...
WHERE (SELECT COUNT(*) FROM orders WHERE customer_id = ? AND status != 'CANCELLED') < 3;

执行后若受影响行数为0,说明校验不通过,直接返回业务错误。

优点

  • 完全依赖数据库原子性,无需额外并发控制逻辑,实现简单。
  • 从根源避免竞态条件,单条SQL在数据库内部是原子执行的。

缺点

  • 业务规则硬编码到SQL中,与领域模型(Customer聚合根)的逻辑脱节,违反DDD「业务逻辑内聚到领域层」的核心原则。
  • 后续规则迭代(比如区分订单类型、排除已取消订单)会导致SQL臃肿难维护,且无法通过单元测试覆盖业务逻辑。

方案二:Customer聚合根加版本号(乐观锁)

回归DDD设计思路:让Customer聚合根维护「有效订单数」属性,addOrder方法内校验规则,通过乐观锁解决并发问题:

  1. 从数据库加载包含「订单数」「版本号」的Customer聚合根。
  2. 调用customer.addOrder(),领域层校验订单数是否小于3。
  3. 在同一事务中,插入新订单,同时以「加载时的版本号」为条件更新Customer的订单数和版本号。
  4. 若更新失败(版本号不匹配),说明存在并发修改,触发重试逻辑(前端提示用户稍后再试或后台自动重试)。

优点

  • 业务规则完全内聚在Customer聚合根中,符合DDD架构理念,便于单元测试和规则迭代。
  • 乐观锁性能优于悲观锁,适合大多数高并发场景(只要冲突率不是极高)。

缺点

  • 需要实现重试逻辑,增加了代码复杂度。
  • 若并发冲突率极高,重试会导致性能下降,需额外做降级处理。

更优实现:聚合根乐观锁+事务原子性

基于方案二做DDD适配优化:

  • 把订单创建逻辑封装在Customer聚合根的addOrder方法中,直接生成Order实体(包含所有必要业务属性)。
  • 事务中绑定两个操作:插入Order、更新Customer的订单数和版本号,确保原子性。
  • 用数据库乐观锁(版本号)检测并发冲突,冲突时返回明确的业务错误,由上层决定重试策略。

如果数据库支持特殊锁机制(比如PostgreSQL的SELECT ... FOR UPDATE SKIP LOCKED),在冲突率极高的场景下,可考虑用悲观锁,但需严格控制锁的粒度,避免影响其他业务操作。

总结

若业务规则简单、对DDD架构要求不高,方案一可快速落地;但遵循DDD架构的项目中,方案二(乐观锁)是更合理的选择,它能保持领域模型的完整性,同时解决并发问题。追求极致DDD实践的话,推荐采用「聚合根乐观锁+事务原子性」的优化方案。

内容的提问来源于stack exchange,提问作者fernando1979

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.06 10:12:33