DDD

DDD领域驱动设计完整知识总结

DDD(Domain‑Driven Design)领域驱动设计,是一套针对复杂业务系统的建模方法论,不是框架。
核心思想:把业务领域作为软件系统的核心,数据库、框架只是外围技术;分为战略DDD(划分业务模型边界)与战术DDD(对象建模);常搭配洋葱/六边形架构落地。

适用场景:业务规则复杂、长期迭代的核心业务;简单CRUD系统不建议强行使用DDD,避免过度设计。

一、DDD核心基础概念

  1. 软件的核心是业务领域,而不是数据库、框架。
  2. 先做战略设计(划分边界),再做战术建模(写对象代码)。
  3. 业务规则尽量内聚到领域对象,不要散落在业务Service。
  4. 依赖倒置DIP:领域层(业务内核)不依赖任何外部框架,外部技术适配领域。
  5. 业务不变量:业务上必须永远成立的约束,由聚合根保证。

二、战略DDD:划分业务边界

战略DDD解决:系统怎么切分,模型边界在哪里;很多人忽略这一步直接写代码,导致DDD落地失败。

2.1 领域、子域

  • 领域 Domain:整个软件覆盖的业务范围。
  • 子域 Subdomain(业务视角),把大领域拆分成若干业务子域
    1. 核心子域:企业核心价值业务,重点投入资源建模。例:电商订单。
    2. 支撑子域:保障业务运行,不构成核心竞争力。例:优惠券。
    3. 通用子域:通用能力,优先复用现成组件。例:消息通知、文件存储。

2.2 限界上下文 Bounded Context

限界上下文是模型边界,不是模块、不是包、不是微服务。
在一个限界上下文内部,业务词汇含义统一;出了边界,相同名词可以拥有完全不同含义。

举例:

  • 会员BC中的"用户":userId、手机号、会员等级、积分、地址列表(实体,可编辑)
  • 订单BC中的"用户":仅userId、收件人、收货快照地址,不需要会员等级积分。

如果没有限界上下文,会试图维护一个全局大模型对象,字段无限膨胀,耦合严重。

上下文之间禁止直接共享领域实体对象。交互两种方式:

  1. RPC接口调用,配合防腐层ACL;
  2. 发布/订阅领域事件。

2.3 通用语言

产品、开发、测试使用同一套业务词汇;词汇直接映射类名、方法名。
限界上下文内部通用语言生效;不同上下文,词汇语义可以不一样。

2.4 防腐层 ACL

Anti‑Corruption Layer,防腐层,是一种跨边界的隔离适配机制,实现通常放在基础设施层(interfaces层对接外部API时也可能需要)。
外部上下文返回的数据模型,不能直接在本上下文使用;通过防腐层做模型转换,把外部DTO转为我方领域对象,防止外部系统模型变更污染本上下文领域模型。

2.5 子域 / 限界上下文 / 微服务三者关系

  • 子域:业务能力视角
  • 限界上下文:业务模型边界视角
  • 微服务:部署物理视角

注意对应关系:一个子域可能对应多个限界上下文(大子域拆分),一个限界上下文通常只对应一个子域。禁止简单粗暴地认为"1子域 = 1限界上下文 = 1微服务"。
落地:初期多个简单限界上下文放在同一个服务(不同包);业务膨胀后,再拆分微服务。先逻辑边界,后物理拆分。

三、战术DDD:限界上下文内部对象建模

在一个限界上下文内部,对业务对象建模。

3.1 实体 Entity

  • 拥有全局唯一ID;有生命周期,状态可变;相等判断依据ID。
  • 可以包含业务行为(充血模型)。

示例:订单Order、商品SKU。

3.2 值对象 Value Object

  • 没有独立ID;由一组属性构成;不可变;相等判断对比全部属性。
  • 没有独立的生命周期标识,通常用来描述实体的属性特征。

示例:金额OrderMoney、收货地址ShippingAddress。
不可变:修改时返回新的值对象实例,不修改原有对象。

3.3 聚合 & 聚合根 Aggregate & AggregateRoot

  • 聚合:一组强相关对象,是业务一致性边界;定义事务边界。
  • 聚合根 AR:聚合对外的唯一访问入口。外部只能通过聚合根访问聚合内部子对象。
  • 子实体不能对外暴露,不能有独立Repository。

重要约束:一个事务只修改一个聚合根。
示例:订单聚合

  • 聚合根:Order
  • 子实体:OrderItem订单项;外部不能直接操作订单项,只能通过Order操作。
  • 值对象:OrderMoney、ShippingAddress。
  • 业务不变量:订单总金额=所有订单项金额之和,聚合根内部保证。

3.4 领域服务 Domain Service

存放业务规则,位于 domain/service;无DB、无MQ、无外部接口调用。

适用场景(满足其一就抽领域服务):

  1. 业务行为跨多个聚合根,不属于任何一个聚合;
  2. 业务算法/计算逻辑,无法归属到某一个实体。

只做业务计算与校验,返回计算结果;不做持久化,持久化交给上层应用服务。

3.5 应用服务 Application Service

位于 application 层;不包含核心业务规则。
职责:

  • 用例流程编排;
  • 获取聚合根对象;
  • 事务控制;
  • 调用仓储保存聚合;
  • 拉取、发布领域事件。

区分记忆:

  • 领域服务:管业务规则、计算(domain层)
  • 应用服务:管流程、事务、存储(application层)

3.6 仓储 Repository

DIP依赖倒置落地关键:

  1. Repository接口定义在domain领域层;
  2. Repository实现在infrastructure基础设施层。
  3. Repository面向聚合根;只保存/查询聚合根。子实体不定义Repository。

3.7 领域事件 Domain Event

  • 表达已经发生的业务事实;命名使用过去式,例如OrderCreatedEvent。
  • 在聚合根内部产生,存放事件列表;
  • 保存聚合之后,由应用层拉取并发布事件;
  • 用于解耦业务流程;可以MQ异步消费。

四、DDD + 洋葱架构包结构

com.ecommerce.order          // 订单限界上下文
├── domain                    // 领域层【最内层,不能引入框架】
│   ├── entity                // 实体、聚合根、子实体
│   ├── vo                    // 值对象
│   ├── event                 // 领域事件
│   ├── repository            // Repository【接口】
│   └── service               // 领域服务
├── application               // 应用层,流程编排
│   ├── dto                   // Command/DTO
│   └── service               // 应用服务
├── infrastructure            // 基础设施层
│   ├── repository            // Repository实现类
│   ├── acl                   // 防腐层ACL,对接外部上下文
│   └── message               // MQ事件发布消费
└── interfaces                // 接口层 Controller/RPC
    └── controller

强制约束:domain包下禁止import Spring、MyBatis等框架类。

五、实战案例:多订单优惠券分摊(领域服务示例)

业务场景:一张优惠券,分摊抵扣多个订单金额。

逻辑跨多个Order聚合根,不属于单个Order,放入领域服务。

领域服务(domain层,仅计算,不持久化)

// domain/service/OrderDiscountDomainService
public class OrderDiscountDomainService {
    public List<DiscountAllocationResult> allocateDiscount(
            List<Order> orders,
            CouponDiscount couponDiscount
    ) {
        // 1.业务规则校验
        // 2.比例分摊计算,处理小数精度
        // 3.返回分摊结果值对象
    }
}

应用服务调用

@Service
public class OrderApplicationService {
    @Transactional
    public void applyCoupon(Long couponId, List<Long> orderIdList){
        //1.查询多个订单聚合根
        //2.构建优惠券值对象
        //3.调用领域服务做分摊计算
        //4.使用返回结果修改订单聚合根状态
        //5.保存聚合根、发布领域事件
    }
}

领域服务负责业务计算;应用服务负责流程、事务、存储。

六、充血模型 vs 贫血模型

  1. 贫血模型(反模式)
    领域实体只有getter/setter;所有业务校验、状态逻辑全部写在应用服务。

业务规则散落在Service,领域对象变成单纯数据载体。

  1. 充血模型(DDD提倡)
    业务校验、状态变更、业务不变量逻辑下沉到聚合根/实体内部;
    应用服务只做编排。

示例:order.pay()、order.cancel() 业务方法定义在Order聚合根内部。

七、DDD常见踩坑清单

  1. 跳过战略DDD,直接写实体聚合;限界上下文划分缺失。
  2. Repository接口写在基础设施层,违反DIP依赖倒置。
  3. 子实体单独创建Repository,绕过聚合根操作子对象。
  4. 领域层引入Spring、Mybatis框架依赖。
  5. 贫血模型:实体只有get/set,业务逻辑全部堆在应用服务。
  6. 聚合设计过大,聚合内部塞进大量无关对象;一个事务修改多个聚合根。
  7. 限界上下文直接等同于微服务,上来就拆分微服务。
  8. 简单CRUD业务强行套用DDD全套概念,过度设计。
  9. 领域服务写数据库操作、调用外部接口。领域服务只做业务计算。

八、DDD落地判断:什么时候用,什么时候不用

适合DDD:

  • 业务规则复杂多变;核心业务;长期迭代维护;大量业务概念。

不适合DDD:

  • 简单CRUD系统;报表;一次性原型;业务逻辑单薄。

DDD是有成本的建模工具,不要为炫技而使用。

posted @ 2026-09-06 22:13  灰马非马  阅读(62)  评论(0)    收藏  举报