架构(03):DDD 领域驱动设计——通用语言、聚合、实体、值对象、领域事件
更新时间:2026-09-02。本文是
architecture/架构领域第 03 篇,接 微服务拆分。DDD 的核心是"让软件模型反映业务领域",而不是反过来让业务适配软件模型。通用语言是 DDD 的基础,限界上下文是边界,聚合根是数据一致性边界,领域事件是解耦。
本文要回答的问题
- DDD 和传统 CRUD 有什么区别?为什么要用 DDD?
- 通用语言(Ubiquitous Language)是什么?怎么统一?
- 限界上下文怎么划分?和微服务是什么关系?
- 聚合根、实体、值对象区别是什么?什么场景用什么?
- 领域事件有什么用?什么时候用?
一、DDD 是什么,和传统 CRUD 区别
传统 CRUD 开发:
需求 → 数据库表结构设计 → CRUD → 业务逻辑放在 Service 层
问题:
- 业务逻辑散落在 Service 和 DAO
- 模型就是数据表,不反映业务
- 业务变了,模型跟不上,越积越乱DDD 开发:
业务理解 → 通用语言 → 划分限界上下文 → 设计聚合 → 编码
核心思想:
- 软件模型要反映业务领域
- 每个限界上下文有自己的模型
- 模型用领域对象(实体、值对象)表示
- 业务逻辑在领域对象中,不是 Service 层
结论:DDD 是一种思维方式,不是框架。什么时候用 DDD:
- 业务复杂,业务经常变 → 推荐 DDD
- 业务简单,固定 → 传统 CRUD 够用,不要用 DDD 过度设计
二、战略设计:通用语言 + 限界上下文
通用语言(Ubiquitous Language)
通用语言:业务专家、开发、产品一起统一术语
示例:
业务说"订单",开发叫"order",产品叫"单据"
统一:大家都叫"订单",定义清楚:订单包含什么状态,什么操作
要求:
1. 术语在代码中体现(类名、变量名)
2. 文档、需求、代码用同一个词
3. 歧义:讨论清楚,写下来
为什么重要:
歧义是最大的bug,开发理解错业务,做出来不对
统一术语,减少歧义限界上下文(Bounded Context)
限界上下文:一个领域语义一致的边界
在这个边界内,每个术语都有确定的含义
示例:
电商系统:
订单上下文:"商品"指卖的商品
库存上下文:"商品"指仓库里的 SKU
同一个词,不同上下文含义不同 → 分边界
限界上下文和微服务:
一个限界上下文对应一个微服务
多个小限界上下文也可以合并成一个微服务
不是强制 1:1,主要看边界清晰三、战术设计:聚合、实体、值对象、领域事件
1. 值对象(Value Object)
特点:
- 没有唯一标识
- 不可变
- 只看属性值,两个值相同就是同一个
示例:
地址:省市区街道,两个地址属性相同就是同一个
金额:100 元就是 100 元,没区别
用途:
- 描述属性,不需要 ID
- 封装行为(比如金额相加)
对比:
实体:有 ID,同一个 ID 就是同一个实体,属性可以变
值对象:没 ID,值相同就是同一个,不可变2. 实体(Entity)
特点:
- 有唯一标识(ID)
- 属性可以变,生命周期中 ID 不变
- 有生命周期
示例:
订单:订单 ID 唯一,不管状态怎么变,还是这个订单
用户:用户 ID 不变,信息可以改3. 聚合根(Aggregate Root)
聚合:一组相关领域对象的集合,有一个聚合根
聚合根:
- 聚合的入口,外部只通过聚合根访问
- 维护聚合内部数据一致性
- 聚合根有 ID,唯一标识整个聚合
示例:
订单聚合:
聚合根:Order(订单)
包含:OrderItem(订单项)
外部不能直接访问 OrderItem,只能通过 Order
一致性:订单总金额 = 所有订单项金额之和,Order 维护
数据一致性边界:一个聚合一次事务,只修改一个聚合
不要跨聚合事务,最终一致性就够了聚合设计原则:
- 小聚合,不要太大
- 聚合根只引用其他聚合根的 ID,不要持有整个对象
- 一个事务只修改一个聚合
4. 领域事件(Domain Event)
领域事件:领域中发生的重要事情,值得记录
特点:
- 已经发生,不可变
包含事件信息(谁、什么时候、什么事)
发布之后,其他限界上下文可以监听
示例:
订单创建后,发布 OrderCreatedEvent
库存服务监听,扣库存
支付服务监听,发起支付
用途:
解耦限界上下文
不直接依赖,通过事件通信
适合最终一致性四、DDD 分层
典型 DDD 分层(整洁架构风格):
┌──────────────────────────────────────────────────────────┐
│ 接口层(Interfaces) │
│ Controller / API 网关 │
├──────────────────────────────────────────────────────────┤
│ 应用层(Application Layer) │
│ Use Case 编排,调用领域对象,不包含业务逻辑 │
├──────────────────────────────────────────────────────────┤
│ 领域层(Domain Layer) │
│ 实体、值对象、聚合根、领域事件、业务逻辑在这里 │
├──────────────────────────────────────────────────────────┤
│ 基础设施层(Infrastructure Layer) │
│ 实现领域层定义的 Repository 接口,数据库、外部服务 │
└──────────────────────────────────────────────────────────┘依赖规则:
- 外层依赖内层
- 领域层不依赖任何外层
- 基础设施层实现领域层接口
- 领域层纯业务代码,不依赖框架
五、DDD 落地常见坑
| 坑 | 现象 | 对策 |
|---|---|---|
| 不理解通用语言 | 术语还是乱,歧义多 | 业务专家和开发一起讨论,写下来 |
| 聚合太大 | 跨聚合事务,一致性难维护 | 小聚合,拆分,最终一致性 |
| 贫血模型 | 所有业务逻辑都在 Service,领域对象只是 setter/getter | 把业务逻辑放到领域对象 |
| 过度设计 | 简单业务也用 DDD,复杂度暴涨 | 业务简单就用 CRUD,不要硬套 DDD |
| 领域层依赖基础设施 | 领域层用到 DAO,耦合 | 依赖倒置,领域层定义接口,基础设施实现 |
六、DDD 总结
| 概念 | 一句话 |
|---|---|
| 通用语言 | 业务和开发统一术语,减少歧义 |
| 限界上下文 | 划分边界,边界内语义一致 |
| 值对象 | 无 ID,不可变,属性相等就是同一个 |
| 实体 | 有 ID,属性可变,ID 不变 |
| 聚合根 | 聚合入口,维护一致性,一次事务一个聚合 |
| 领域事件 | 发布事件,解耦上下文,最终一致性 |
相关与延伸
下一篇:CQRS 与 Event Sourcing——读写分离、事件溯源、重放;微服务拆分,见 微服务拆分——限界上下文、数据一致性。
一句话总结
DDD 领域驱动设计:核心是让软件模型反映业务领域,不是让业务适配软件模型;战略设计:通用语言统一术语减少歧义,限界上下文划分边界(对应微服务);战术设计:值对象(无 ID 不可变)、实体(有 ID 属性可变)、聚合根(聚合入口,维护一致性,一次事务一个聚合)、领域事件(发布事件解耦上下文);分层:接口层 → 应用层(编排) → 领域层(业务逻辑) → 基础设施层(实现);适合业务复杂经常变,简单业务用 CRUD 不要硬套;常见坑:聚合太大、贫血模型、过度设计。