领域驱动设计

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

DDD 分层与六边形架构与整洁架构

第一性原理层(Why)——DDD 为什么存在

复杂系统的根本矛盾

DDD 解决的根本问题:如何在长期演进的复杂系统中,让多个角色对"系统是什么、在做什么"保持一致理解。

DDD 的第一性原理

原理本质解释
语言即模型语言是认知的载体,不一致的语言必然导致不一致的系统
边界先于实现不清晰的边界会放大复杂度
不变性优先稳定的不变性是系统演进的锚点
模型是认知压缩好模型 = 用更少概念表达更多规则
架构是组织的映射系统结构反映沟通结构(Conway 定律)

DDD 核心概念关系图

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

认知与语言层(What)——我们如何理解业务世界

领域(Domain)

领域 = 一个被语言和规则定义的问题空间

子域(Subdomain)

子域是对领域的问题拆分方式

子域类型认知价值战略意义
核心子域差异化竞争力投入最强人力
支撑子域支持核心可内部实现
通用子域通用能力可购买/外包

子域划分的本质:资源配置与注意力分配。

通用语言(Ubiquitous Language)

通用语言 = 团队共享的业务世界观

原则

模型层(How)——如何构建可演化的认知结构

模型的定义

模型 = 对现实知识的有意简化 + 结构化表达

模型的价值不在"是否完整",而在:

有效建模的判定标准

维度判定问题
可执行性是否能指导代码结构?
表达力是否自然表达业务规则?
稳定性是否围绕不变性构建?
演化性是否允许逐步精进?

模型与实现的闭环

脱离实现的模型必然腐化

战术设计层(Structure)——模型如何落地为结构

本层关注:在单一边界内,如何保持模型一致性

实体(Entity)

设计原理:业务中存在需要追踪生命周期、拥有唯一身份的对象。实体通过身份标识而非属性值判断相等性。

核心要点:相等性基于身份,不是属性。订单 A 和订单 B 即使内容完全相同,也是不同的实体。

常见误用:将所有对象都设计为实体,导致系统复杂度爆炸。应该优先考虑值对象。

值对象(Value Object)

设计原理:某些概念本质上是对某个度量、描述或约束的表达,不需要身份追踪。值对象通过属性值判断相等性,且应该不可变。

核心要点:相等性基于属性值。两个 Money(100, USD) 是相同的,无论何时创建。不可变性保证了值对象的安全性和可预测性。

常见误用:将值对象设计为可变,导致难以追踪状态变化。

聚合(Aggregate)

设计原理:在复杂业务中,多个实体和值对象需要协作维护一致性。聚合是一致性与不变性的最小边界——聚合内部强一致,聚合之间通过事件最终一致。

聚合 = 一致性与不变性的最小边界

核心原则

  1. 只通过聚合根访问
  2. 聚合内强一致
  3. 聚合之间最终一致
  4. 聚合要"小而自洽"

核心要点:聚合边界的划分决定了并发冲突的粒度。过大的聚合导致频繁锁定,过小的聚合导致频繁跨聚合操作。聚合根是唯一的访问入口,保证了内部一致性规则的执行。

常见误用:聚合边界划分过大,导致并发冲突和性能问题;或过小,导致频繁跨聚合操作。

领域服务(Domain Service)

设计原理:某些业务行为跨越多个实体,但仍属于领域逻辑。这些行为不应该强行放入某个实体,而应该用领域服务表达。

核心要点:领域服务是对跨聚合业务逻辑的表达,但不应该成为贫血对象的容器。如果发现领域服务中有大量逻辑,说明可能需要重新审视聚合边界。

常见误用:将所有业务逻辑都放入领域服务,导致实体变成贫血对象。

Repository & Factory

设计原理

核心要点:Factory 关注"如何创建",Repository 关注"如何获取已存在的对象"。两者都是为了隐藏复杂性,让调用者专注于业务逻辑。

二者的本质区别在于:创建 vs 已存在对象的获取

边界与协作层(Boundary)——如何在组织中保持一致性

限界上下文(Bounded Context)

限界上下文 = 语言与模型的生效范围

上下文映射(Context Mapping)

上下文关系 = 组织协作关系

映射策略对比

策略场景权力关系适用条件集成成本
共享内核(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

上下文职责

演进与重构层(Evolution)——模型如何随认知成长

重构的本质

DDD 重构 = 模型认知升级,而非代码洁癖

模型重构的根本驱动力是认知的深化。当团队对业务的理解超越了当前模型的表达能力,重构就是必然的。

触发信号

信号类型具体表现根本原因
语言失效团队开始用"绕行说法"描述业务模型无法承载新业务概念
隐式规则泛滥大量业务规则散落在服务层领域概念未被对象化
边界模糊一个改动牵动多个聚合聚合边界划分失当
模型僵化新需求总是"不得不"绕过模型核心抽象已不再稳定
认知分裂不同团队对同一概念理解不一致通用语言已发生漂移

认知突破的来源

模型进化的本质是认知突破,而认知突破来自深度对话

从隐式到显式

DDD 演进的方向:让隐藏在代码注释和口口相传中的业务规则,成为可执行的模型对象

三种提炼手法

手法本质示例
概念提炼命名一个之前无名的概念发现"退款窗口期"是独立的业务规则,提炼为值对象
引入 Specification将复杂业务判断规则对象化,使其可组合、可测试OrderEligibleForRefund 替代散落各处的 if 判断
过程行为对象化将流程性逻辑封装为领域服务或策略对象将定价逻辑从服务层提炼为 PricingPolicy

常见重构模式

模式触发场景核心动作
拆分聚合聚合过大、事务边界不清识别独立不变量,分裂为多个小聚合
提炼子域某块业务增长为独立关注点从大上下文中划出新的限界上下文
引入领域事件上下文间耦合过紧用事件替代直接调用,解耦协作关系
统一通用语言团队用词出现分歧召集领域专家重新对齐语言,同步到代码
上下文边界迁移某个概念归属模糊重新谈判边界,搬移模型到正确上下文

演进节奏与风险控制

模型突破往往意味着系统级调整,需要有节奏地推进

风险来源:模型重构不仅是技术动作,还是组织认知的同步过程。代码改了但语言没改、文档没改,重构就是半成品。

安全演进的节奏

1. 语言先行  → 先在团队中对齐新概念,确认认知一致2. 模型试点  → 在新代码路径中使用新模型,旧路径保持不动3. 双轨并行  → 新旧模型共存,通过防腐层隔离4. 逐步迁移  → 将旧逻辑逐步迁移到新模型5. 清除遗留  → 确认迁移完成后,删除旧模型

团队认知同步

模型变了,通用语言必须同步变,否则重构无效

这是 DDD 演进中最常被忽视的环节:

战略设计层(Direction)——长期竞争力的来源

战略设计的两个维度

核心域(Core Domain)

核心域 = 组织最值得投入认知与人才的地方

特征说明
高复杂度需要深度专业知识和持续投入
高差异化构成企业核心竞争力
高回报投入产出比最高

核心域的现实困境:随着系统演进,核心域的代码往往会被通用逻辑侵蚀、边界模糊,导致最重要的业务逻辑淹没在技术噪音中。精炼策略就是用来重新找回核心域纯净性的工具。

精炼策略

策略解决的问题本质动作适用场景
Generic Subdomain(通用子域剥离)核心域混入了大量通用能力(如权限、日志、消息),稀释了核心注意力识别并踢出非差异化能力,交给外部系统或独立团队系统中存在大量与业务竞争力无关的通用模块
Segregated Core(隔离核心)核心域代码与支撑逻辑混在同一模块,难以识别和保护核心在同一上下文内,将核心代码物理隔离为独立包/模块核心域已识别,但边界在代码层面仍不清晰
Abstract Core(抽象核心)多个子域重复表达同一套核心概念,导致概念漂移和重复建模将跨子域共享的不变性向上提炼为抽象层多个限界上下文存在共享的核心业务概念

三者关系:Generic Subdomain 解决"混入了什么不该有的",Segregated Core 解决"核心在哪里看不清",Abstract Core 解决"核心被重复表达了"。三者可组合使用,共同服务于同一个目标——让核心域保持纯粹、可识别、可演进。

大规模结构(Large-Scale Structure)

大规模结构的本质:在多个限界上下文之上,建立统一的组织原则,使系统整体具有可理解性一致性

根本问题:限界上下文数量增长后,系统整体复杂度超越人类认知上限。

大规模结构的价值

四种大规模结构模式的本质

模式核心思想解决的根本问题何时选择
系统隐喻用一个简单比喻描述整体架构降低认知成本,建立统一心智模型系统需要被广泛理解时
职责分层按职责抽象级别纵向分层分离关注点,建立依赖方向系统需要清晰的依赖关系时
知识层分离"规则定义"与"规则执行"应对策略频繁变化的复杂度业务规则需要高度灵活性时
可插拔框架核心稳定、扩展可变支持第三方扩展,保持核心稳定需要生态扩展时

系统隐喻(System Metaphor)

系统隐喻 = 用一个简单的比喻来描述系统整体架构

本质:通过类比已知系统,降低对新系统的理解成本。

核心价值

设计原则

风险:隐喻可能掩盖真实业务逻辑,导致设计僵化。

职责分层(Responsibility Layers)

职责分层 = 按职责类型对多个限界上下文进行分层组织

本质:每一层代表一种职责抽象级别,上层依赖下层,下层独立于上层。

核心价值

分层原则

与限界上下文的关系

知识层(Knowledge Level)

知识层 = 将"业务规则的定义"与"业务规则的执行"分离

本质:当系统需要支持多种业务策略、且策略频繁变化时,将策略定义抽象为独立的知识层。

核心价值

何时选择

风险:过度设计——简单场景引入知识层反而增加复杂度。

可插拔组件框架(Pluggable Component Framework)

可插拔组件框架 = 定义一组接口和协议,允许第三方组件动态扩展系统能力

本质:将系统核心能力与扩展能力分离,通过标准化接口实现即插即用。

核心价值

设计原则

与限界上下文的关系

大规模结构的选择原则

考量维度决策问题指导原则
认知成本新成员能否快速理解系统整体?优先选择系统隐喻
依赖管理系统依赖关系是否清晰?优先选择职责分层
变化频率业务规则是否频繁变化?优先选择知识层
扩展需求是否需要第三方扩展?优先选择可插拔框架

核心原则:大规模结构是认知工具,而非技术约束。选择的标准是"是否降低了整体复杂度",而非"是否符合某种模式"。

适用边界与常见误区

DDD 不解决的问题

常见误区

反模式与陷阱

聚合边界划分错误

问题:聚合边界过大或过小,导致并发冲突或频繁跨聚合操作。

表现

修复方案

过度设计(过多的值对象)

问题:为了追求"纯净"而创建过多值对象,导致代码复杂度反而增加。

表现

修复方案

通用语言漂移

问题:团队对同一术语的理解逐渐分化,导致代码与业务脱节。

表现

修复方案

限界上下文泄露

问题:限界上下文的边界不清晰,导致模型污染和耦合。

表现

修复方案

演进阶段指导

单体应用中的 DDD

特点:所有限界上下文在同一进程中运行。

应用方式

核心要点:在单体应用中,DDD 的价值在于通过清晰的模型和边界管理复杂度,为未来的微服务化做准备。

微服务中的 DDD

特点:每个限界上下文对应一个微服务。

应用方式

核心要点:微服务中的 DDD 强调上下文的独立性和自治性。每个服务维护自己的聚合,通过事件驱动实现最终一致性。

总结:DDD 的真正价值

DDD 的终极价值不在于"代码长什么样",而在于:

关联内容(自动生成)