DDD 不是一套建模技巧集合,而是一种在复杂业务系统中,通过语言、边界与模型压缩认知复杂度、协调组织协作的系统性方法论。

DDD 解决的根本问题:如何在长期演进的复杂系统中,让多个角色对"系统是什么、在做什么"保持一致理解。
| 原理 | 本质解释 |
|---|---|
| 语言即模型 | 语言是认知的载体,不一致的语言必然导致不一致的系统 |
| 边界先于实现 | 不清晰的边界会放大复杂度 |
| 不变性优先 | 稳定的不变性是系统演进的锚点 |
| 模型是认知压缩 | 好模型 = 用更少概念表达更多规则 |
| 架构是组织的映射 | 系统结构反映沟通结构(Conway 定律) |
graph TD
A["领域<br/>(Domain)"] --> B["子域<br/>(Subdomain)"]
B --> C["限界上下文<br/>(Bounded Context)"]
C --> D["聚合<br/>(Aggregate)"]
D --> E["实体<br/>(Entity)"]
D --> F["值对象<br/>(Value Object)"]
E --> G["业务行为"]
F --> H["不可变约束"]
I["通用语言<br/>(Ubiquitous Language)"] -.->|贯穿| A
I -.->|贯穿| C
I -.->|贯穿| D
style A fill:#e1f5ff
style C fill:#fff3e0
style D fill:#f3e5f5
style E fill:#e8f5e9
style F fill:#fce4ec
领域 = 一个被语言和规则定义的问题空间
子域是对领域的问题拆分方式:
| 子域类型 | 认知价值 | 战略意义 |
|---|---|---|
| 核心子域 | 差异化竞争力 | 投入最强人力 |
| 支撑子域 | 支持核心 | 可内部实现 |
| 通用子域 | 通用能力 | 可购买/外包 |
子域划分的本质:资源配置与注意力分配。
通用语言 = 团队共享的业务世界观
原则:
模型 = 对现实知识的有意简化 + 结构化表达
模型的价值不在"是否完整",而在:
| 维度 | 判定问题 |
|---|---|
| 可执行性 | 是否能指导代码结构? |
| 表达力 | 是否自然表达业务规则? |
| 稳定性 | 是否围绕不变性构建? |
| 演化性 | 是否允许逐步精进? |
脱离实现的模型必然腐化
本层关注:在单一边界内,如何保持模型一致性
设计原理:业务中存在需要追踪生命周期、拥有唯一身份的对象。实体通过身份标识而非属性值判断相等性。
核心要点:相等性基于身份,不是属性。订单 A 和订单 B 即使内容完全相同,也是不同的实体。
常见误用:将所有对象都设计为实体,导致系统复杂度爆炸。应该优先考虑值对象。
设计原理:某些概念本质上是对某个度量、描述或约束的表达,不需要身份追踪。值对象通过属性值判断相等性,且应该不可变。
核心要点:相等性基于属性值。两个 Money(100, USD) 是相同的,无论何时创建。不可变性保证了值对象的安全性和可预测性。
常见误用:将值对象设计为可变,导致难以追踪状态变化。
设计原理:在复杂业务中,多个实体和值对象需要协作维护一致性。聚合是一致性与不变性的最小边界——聚合内部强一致,聚合之间通过事件最终一致。
聚合 = 一致性与不变性的最小边界
核心原则:
核心要点:聚合边界的划分决定了并发冲突的粒度。过大的聚合导致频繁锁定,过小的聚合导致频繁跨聚合操作。聚合根是唯一的访问入口,保证了内部一致性规则的执行。
常见误用:聚合边界划分过大,导致并发冲突和性能问题;或过小,导致频繁跨聚合操作。
设计原理:某些业务行为跨越多个实体,但仍属于领域逻辑。这些行为不应该强行放入某个实体,而应该用领域服务表达。
核心要点:领域服务是对跨聚合业务逻辑的表达,但不应该成为贫血对象的容器。如果发现领域服务中有大量逻辑,说明可能需要重新审视聚合边界。
常见误用:将所有业务逻辑都放入领域服务,导致实体变成贫血对象。
设计原理:
核心要点:Factory 关注"如何创建",Repository 关注"如何获取已存在的对象"。两者都是为了隐藏复杂性,让调用者专注于业务逻辑。
二者的本质区别在于: 创建 vs 已存在对象的获取
限界上下文 = 语言与模型的生效范围边界
在大型系统中,同一个词在不同上下文里代表不同概念——"账户"在财务上下文是资产负债结构,在用户上下文是认证凭据,在游戏上下文是角色档案。如果强行统一为一个"通用账户模型",结果是每个上下文都要为自己不关心的字段买单,模型变得臃肿且无法演进。
限界上下文的本质是拒绝语义上的全局一致性,转而在每个边界内维护局部一致性——边界内语言精确、模型清晰,边界间通过显式协议通信。
这是 DDD 中最常被混淆的概念:
| 维度 | 子域(Subdomain) | 限界上下文(Bounded Context) |
|---|---|---|
| 属于 | 问题空间(Problem Space) | 解决方案空间(Solution Space) |
| 定义者 | 业务本身决定 | 团队/架构师划分 |
| 可变性 | 相对稳定 | 可按需调整 |
| 关系 | 一个子域可对应多个上下文 | 一个上下文可跨越多个子域 |
子域描述"业务问题的自然边界",限界上下文是"我们决定如何建模它"的解决方案边界。理想情况下二者对齐,现实中经常需要权衡。
语言分歧是最强烈的信号:当同一术语在不同场合被不同方式使用时,说明语义边界已经存在,只是没有被显式化。
常见的划分依据:
| 依据 | 说明 |
|---|---|
| 语言断层 | 同一概念在两个团队/场景中含义不同 |
| 不变量隔离 | 某些业务规则只在特定范围内成立 |
| 团队自治边界 | 独立部署、独立发布、独立演进的能力边界 |
| 数据所有权 | 谁有权修改这条数据,谁拥有该上下文 |
| 生命周期差异 | 概念在不同上下文中的生命周期不同(如"订单"从下单到发货是一段,从财务角度是另一段) |
上下文关系 = 组织协作关系
映射策略对比:
| 策略 | 场景 | 权力关系 | 适用条件 | 集成成本 |
|---|---|---|---|---|
| 共享内核(Shared Kernel) | 强协作、强共享 | 对等 | 同一团队、模型高度稳定 | 低(但变更需双方协商) |
| 合作关系(Partnership) | 共同演进、强依赖 | 对等 | 两个上下文紧密配合、共同规划发布 | 中 |
| 客户/供应商(Customer/Supplier) | 上下游依赖 | 非对等(下游可影响上游) | 下游团队能参与上游规划 | 中 |
| 遵奉者(Conformist) | 完全跟随上游 | 非对等(上游主导) | 下游无法影响上游、愿意接受上游模型 | 低(但模型侵蚀风险高) |
| 防腐层(Anti-Corruption Layer) | 受制于外部 | 非对等(保护自身) | 依赖遗留系统或第三方,不愿被污染 | 高 |
| 开放主机服务(Open Host Service) | 服务化输出 | 上游主导 | 作为平台方,服务多个下游消费者 | 中 |
| 发布语言(Published Language) | 标准化集成 | 上游主导 | 需要通用的、文档化的交换格式(常与开放主机服务组合使用) | 中 |
| 各行其道(Separate Ways) | 收益不足以集成 | 无关 | 上下文独立性强,集成复杂度超过收益 | 无 |
关键区分:遵奉者 vs 防腐层——同样是下游跟随上游,遵奉者直接接受上游模型(主动放弃隔离),防腐层主动建立翻译层(保护内部模型)。
决策框架:
选择上下文映射策略时考虑:
1. 团队权力关系
├─ 对等协作 → 共享内核 / 合作关系
├─ 上下游、下游有话语权 → 客户/供应商
└─ 上下游、下游无话语权 → 遵奉者 / 防腐层
2. 是否需要隔离外部模型
├─ 需要隔离(第三方/遗留系统)→ 防腐层
└─ 可接受上游模型 → 遵奉者
3. 作为服务提供方
├─ 多个下游消费者 → 开放主机服务
└─ 需要标准交换格式 → 发布语言(配合开放主机服务)
4. 集成收益判断
└─ 集成成本 > 收益 → 各行其道
伪代码示例(订单与支付上下文):
// 防腐层模式:隔离外部支付系统的变化
class PaymentAntiCorruptionLayer {
// 核心职责:转换外部模型 ↔ 内部模型
processPayment(order: Order): PaymentResult {
// 1. 转换:内部模型 → 外部模型
// 2. 调用:外部支付系统
// 3. 转换:外部模型 → 内部模型
}
}
核心要点:防腐层的本质是隔离外部系统的变化,通过转换层将外部模型转换为内部模型。这样即使外部系统变化,内部模型保持稳定。
常见误用:上下文映射策略选择不当,导致过度耦合或过度分离。
graph LR
A["订单上下文<br/>(Order Context)"] -->|OrderCreatedEvent| B["支付上下文<br/>(Payment Context)"]
B -->|PaymentCompletedEvent| A
A -->|InventoryReservedEvent| C["库存上下文<br/>(Inventory Context)"]
C -->|InventoryReservedEvent| A
A -->|ShipmentCreatedEvent| D["发货上下文<br/>(Shipment Context)"]
D -->|ShipmentCompletedEvent| A
style A fill:#e3f2fd
style B fill:#f3e5f5
style C fill:#e8f5e9
style D fill:#fff3e0
上下文职责:
DDD 重构 = 模型认知升级,而非代码洁癖
模型重构的根本驱动力是认知的深化。当团队对业务的理解超越了当前模型的表达能力,重构就是必然的。
触发信号:
| 信号类型 | 具体表现 | 根本原因 |
|---|---|---|
| 语言失效 | 团队开始用"绕行说法"描述业务 | 模型无法承载新业务概念 |
| 隐式规则泛滥 | 大量业务规则散落在服务层 | 领域概念未被对象化 |
| 边界模糊 | 一个改动牵动多个聚合 | 聚合边界划分失当 |
| 模型僵化 | 新需求总是"不得不"绕过模型 | 核心抽象已不再稳定 |
| 认知分裂 | 不同团队对同一概念理解不一致 | 通用语言已发生漂移 |
模型进化的本质是认知突破,而认知突破来自深度对话
DDD 演进的方向:让隐藏在代码注释和口口相传中的业务规则,成为可执行的模型对象
三种提炼手法:
| 手法 | 本质 | 示例 |
|---|---|---|
| 概念提炼 | 命名一个之前无名的概念 | 发现"退款窗口期"是独立的业务规则,提炼为值对象 |
| 引入 Specification | 将复杂业务判断规则对象化,使其可组合、可测试 | OrderEligibleForRefund 替代散落各处的 if 判断 |
| 过程行为对象化 | 将流程性逻辑封装为领域服务或策略对象 | 将定价逻辑从服务层提炼为 PricingPolicy |
| 模式 | 触发场景 | 核心动作 |
|---|---|---|
| 拆分聚合 | 聚合过大、事务边界不清 | 识别独立不变量,分裂为多个小聚合 |
| 提炼子域 | 某块业务增长为独立关注点 | 从大上下文中划出新的限界上下文 |
| 引入领域事件 | 上下文间耦合过紧 | 用事件替代直接调用,解耦协作关系 |
| 统一通用语言 | 团队用词出现分歧 | 召集领域专家重新对齐语言,同步到代码 |
| 上下文边界迁移 | 某个概念归属模糊 | 重新谈判边界,搬移模型到正确上下文 |
模型突破往往意味着系统级调整,需要有节奏地推进
风险来源:模型重构不仅是技术动作,还是组织认知的同步过程。代码改了但语言没改、文档没改,重构就是半成品。
安全演进的节奏:
1. 语言先行 → 先在团队中对齐新概念,确认认知一致
2. 模型试点 → 在新代码路径中使用新模型,旧路径保持不动
3. 双轨并行 → 新旧模型共存,通过防腐层隔离
4. 逐步迁移 → 将旧逻辑逐步迁移到新模型
5. 清除遗留 → 确认迁移完成后,删除旧模型
模型变了,通用语言必须同步变,否则重构无效
这是 DDD 演进中最常被忽视的环节:
DDD分层架构 = 将领域知识与技术实现分离的横向切分
| 层次 | 职责 | 核心组件 | 关键特征 |
|---|---|---|---|
| Interfaces | 外部交互边界 | DTO、Assembler、Facade | 协议转换、输入验证 |
| Applications | 工作流程编排 | Application Service | 薄层:仅协调,不含业务逻辑 |
| Domain | 业务概念与规则核心 | Entity、VO、Aggregate、Domain Event | 厚层:承载几乎全部业务逻辑,禁止依赖其他层 |
| Infrastructure | 技术支撑 | Repository实现、数据库、消息队列 | 实现 Domain 接口,隔离技术细节 |
Interfaces → Applications → Domain ← Infrastructure
战略设计的两个维度:
- **微观战略**:子域分类、限界上下文、上下文映射(已在前文详述)
- **宏观战略**:大规模结构(Large-Scale Structure),用于组织多个限界上下文的整体架构
核心域 = 组织最值得投入认知与人才的地方
| 特征 | 说明 |
|---|---|
| 高复杂度 | 需要深度专业知识和持续投入 |
| 高差异化 | 构成企业核心竞争力 |
| 高回报 | 投入产出比最高 |
核心域的现实困境:随着系统演进,核心域的代码往往会被通用逻辑侵蚀、边界模糊,导致最重要的业务逻辑淹没在技术噪音中。精炼策略就是用来重新找回核心域纯净性的工具。
精炼策略:
| 策略 | 解决的问题 | 本质动作 | 适用场景 |
|---|---|---|---|
| Generic Subdomain(通用子域剥离) | 核心域混入了大量通用能力(如权限、日志、消息),稀释了核心注意力 | 识别并踢出非差异化能力,交给外部系统或独立团队 | 系统中存在大量与业务竞争力无关的通用模块 |
| Segregated Core(隔离核心) | 核心域代码与支撑逻辑混在同一模块,难以识别和保护核心 | 在同一上下文内,将核心代码物理隔离为独立包/模块 | 核心域已识别,但边界在代码层面仍不清晰 |
| Abstract Core(抽象核心) | 多个子域重复表达同一套核心概念,导致概念漂移和重复建模 | 将跨子域共享的不变性向上提炼为抽象层 | 多个限界上下文存在共享的核心业务概念 |
三者关系:Generic Subdomain 解决"混入了什么不该有的",Segregated Core 解决"核心在哪里看不清",Abstract Core 解决"核心被重复表达了"。三者可组合使用,共同服务于同一个目标——让核心域保持纯粹、可识别、可演进。
大规模结构的本质:在多个限界上下文之上,建立统一的组织原则,使系统整体具有可理解性和一致性。
根本问题:限界上下文数量增长后,系统整体复杂度超越人类认知上限。
大规模结构的价值:
| 模式 | 核心思想 | 解决的根本问题 | 何时选择 |
|---|---|---|---|
| 系统隐喻 | 用一个简单比喻描述整体架构 | 降低认知成本,建立统一心智模型 | 系统需要被广泛理解时 |
| 职责分层 | 按职责抽象级别纵向分层 | 分离关注点,建立依赖方向 | 系统需要清晰的依赖关系时 |
| 知识层 | 分离"规则定义"与"规则执行" | 应对策略频繁变化的复杂度 | 业务规则需要高度灵活性时 |
| 可插拔框架 | 核心稳定、扩展可变 | 支持第三方扩展,保持核心稳定 | 需要生态扩展时 |
系统隐喻 = 用一个简单的比喻来描述系统整体架构
本质:通过类比已知系统,降低对新系统的理解成本。
核心价值:
设计原则:
风险:隐喻可能掩盖真实业务逻辑,导致设计僵化。
职责分层 = 按职责类型对多个限界上下文进行分层组织
本质:每一层代表一种职责抽象级别,上层依赖下层,下层独立于上层。
核心价值:
分层原则:
与限界上下文的关系:
知识层 = 将"业务规则的定义"与"业务规则的执行"分离
本质:当系统需要支持多种业务策略、且策略频繁变化时,将策略定义抽象为独立的知识层。
核心价值:
何时选择:
风险:过度设计——简单场景引入知识层反而增加复杂度。
可插拔组件框架 = 定义一组接口和协议,允许第三方组件动态扩展系统能力
本质:将系统核心能力与扩展能力分离,通过标准化接口实现即插即用。
核心价值:
设计原则:
与限界上下文的关系:
| 考量维度 | 决策问题 | 指导原则 |
|---|---|---|
| 认知成本 | 新成员能否快速理解系统整体? | 优先选择系统隐喻 |
| 依赖管理 | 系统依赖关系是否清晰? | 优先选择职责分层 |
| 变化频率 | 业务规则是否频繁变化? | 优先选择知识层 |
| 扩展需求 | 是否需要第三方扩展? | 优先选择可插拔框架 |
核心原则:大规模结构是认知工具,而非技术约束。选择的标准是"是否降低了整体复杂度",而非"是否符合某种模式"。
问题:聚合边界过大或过小,导致并发冲突或频繁跨聚合操作。
表现:
修复方案:
问题:为了追求"纯净"而创建过多值对象,导致代码复杂度反而增加。
表现:
修复方案:
问题:团队对同一术语的理解逐渐分化,导致代码与业务脱节。
表现:
修复方案:
问题:限界上下文的边界不清晰,导致模型污染和耦合。
表现:
修复方案:
特点:所有限界上下文在同一进程中运行。
应用方式:
核心要点:在单体应用中,DDD 的价值在于通过清晰的模型和边界管理复杂度,为未来的微服务化做准备。
特点:每个限界上下文对应一个微服务。
应用方式:
核心要点:微服务中的 DDD 强调上下文的独立性和自治性。每个服务维护自己的聚合,通过事件驱动实现最终一致性。
DDD 定义原则,事件风暴和六边形架构提供操作技术。
本质:通过团队工作坊协作发现领域事件、构建模型的流程技术。
核心元素(用不同颜色即时贴标识):
| 元素 | 规则 | 示例 |
|---|---|---|
| 领域事件 | 过去式 | 订单已创建 |
| 命令 | 祈使句 | 创建订单 |
| 参与者 | 角色名 | 用户、系统 |
| 聚合 | 业务概念边界 | 订单聚合 |
流程:识别领域事件 → 追溯命令和参与者 → 提取聚合 → 划定限界上下文
本质:通过"端口 + 适配器"将领域核心与技术实现分离。
外部 ── 适配器 ── 端口 ── 领域核心 ── 端口 ── 适配器 ── 外部
↓ ↓
Web/API DB/MQ
与 DDD 对应:Domain = 六边形核心,Application = 输入端口,Infrastructure = 输出适配器
本质:CQRS 将读写分离,与 DDD 正交。DDD 专注写操作建模,CQRS 优化读操作。
结合:命令侧用 DDD 聚合维护业务规则,通过事件同步到查询侧构建读模型。
警示:CQRS 本身简单,与 DDD 非强绑定,组合使用增加复杂度,需基于场景决策。
本质:存储事件序列而非当前状态,当前状态由事件回放得出。
传统:状态 → 存储 → 查询 事件溯源:事件序列 → 回放 → 状态
DDD 的终极价值不在于"代码长什么样",而在于: