Model software around the business domain using bounded contexts, aggregates, and ubiquitous language. Use when the user mentions "domain modeling", "bounded context", "aggregate root", "ubiquitous language", "anti-corruption layer", "context mapping", "domain events", "strategic design", "the code doesnt match the business", or "how do we split this big system". Also trigger when breaking a monolith into services, defining service boundaries, or aligning code structure with business processes. Covers entities vs value objects, domain events, and context mapping strategies. For architecture layers, see clean-architecture. For complexity, see software-design-philosophy.
领域驱动设计框架
通过围绕业务领域建模代码来应对软件复杂性的框架。软件最大的风险不是技术失败——而是构建了一个不能反映业务实际运作方式的模型。
核心原则
模型即代码,代码即模型。 软件应体现对业务领域的深刻、共享的理解。当领域专家和开发者使用相同的语言,并且该语言直接在代码库中表达时,复杂性变得可控,系统随着业务变化而优雅演进。
评分
目标:10/10。 通过为快速诊断的每一行满足条件授予1分(共7行),再加上最多3分的深度分:如果核心领域拥有真正丰富的模型(不仅仅是CRUD)加1分,如果不变量存在于聚合内部而非服务中加1分,如果通用语言在对话、代码和测试中保持一致加1分。等级:9-10 = 专家可读的名称、带有防腐层的显式上下文边界、小聚合、行为丰富的实体、用于跨聚合流程的事件、已识别的核心领域;5-6 = 一些领域语言但边界泄漏或贫血对象;<=3 = 技术命名、单一模型适用于所有、逻辑分散在服务中。报告分数和失败的具体诊断行。
框架
1. 通用语言
核心概念: 开发者和领域专家之间共享的、严谨的语言,在对话、文档和代码中一致使用。当语言变化时,代码也随之变化——代码中别扭的命名反过来推动语言的精炼。
为什么有效: 歧义是大多数建模失败的根本原因。当开发者说“订单”而专家指的是“采购请求”时,错误不可避免;通用语言强制代码中的每个名称映射到业务认可和验证的概念。
关键见解:
- 语言来自深度协作,而非事后附加的词汇表
- 如果概念难以命名,模型很可能错了——命名困难是一个设计信号
- 技术术语(
DataProcessorvs.ClaimAdjudicator)向可能纠正它的专家隐藏了领域逻辑 - 不同的限界上下文可能对同一个词有不同的含义——这没问题
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 类/方法命名 | 以领域概念和动词命名 | LoanApplication, policy.underwrite() —— 而不是 RequestHandler, process() |
| 模块结构 | 按领域概念组织 | shipping/, billing/ —— 而不是 controllers/, services/ |
| 代码审查 | 拒绝纯技术名称 | 将 Manager, Helper, Processor, Utils 标记为命名坏味道 |
参见:references/ubiquitous-language.md 在进行建模会话或维护词汇表时使用——涵盖语言如何演进并反馈到代码中。
2. 限界上下文和上下文映射
核心概念: 限界上下文是一个显式边界,在该边界内应用特定的领域模型。同一个词(“Customer”)在不同上下文中可能有不同含义;上下文映射定义了它们之间的关系和转换策略。
为什么有效: 试图维护单一统一模型的大型系统最终会陷入不一致。限界上下文承认业务的不同部分需要不同的模型;上下文映射管理它们之间的集成。
关键见解:
- 限界上下文不是微服务——它是一个语言和模型边界,可能包含多个服务
- 上下文边界通常与团队边界一致(康威定律)
- 九种上下文映射模式描述了团队之间的政治和技术关系
- 防腐层是最重要的防御模式——绝不让外来模型泄漏到你的核心领域
- 共享内核耦合两个团队;保持其小而明确治理
- 从映射现有系统(大泥球)开始,然后定义目标边界
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 服务集成 | 防腐层 | 在边界处将外部API响应转换为你的领域对象 |
| 遗留系统迁移 | 追随者/防腐层 | 将遗留系统包装在适配器后面,该适配器使用你的领域语言 |
| API设计 | 开放主机服务+发布语言 | 公开具有规范模式的良好文档化的REST API |
参见:references/bounded-contexts.md 了解九种映射模式和集成策略。
3. 实体、值对象和聚合
核心概念: 实体具有跨状态变化持续存在的标识。值对象完全由其属性定义且不可变。聚合是实体和值对象的集群,具有强制一致性边界的单一根。
为什么有效: 没有这些区分,所有东西都变成可变的、具有标识的对象——状态混乱、更新不一致、并发脆弱。聚合划定了界限:内部保证一致,外部最终一致。
关键见解:
- 实体测试:“即使我的所有属性都改变,我仍然是同一个东西吗?”(一个人改变姓名和地址——仍然是同一个人)
- 值对象测试:“我仅由我的属性定义吗?”(任何一张10美元钞票与另一张可互换)
- 大多数东西应该是值对象,而不是实体——优先考虑不可变性
- 保持聚合小(一个根加上最小集群);通过ID引用其他聚合,而不是对象引用
- 仅在聚合内部保持立即一致性;聚合之间设计为最终一致性
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 标识跟踪 | 带ID的实体 | Order 由 orderId 标识,跨状态变化存活 |
| 不可变属性 | 值对象 | Address(street, city, zip) —— 替换,绝不修改 |
| 一致性边界 | 聚合根 | Order 是根;OrderLine 项仅通过它存在 |
| 并发控制 | 根上的乐观锁 | Order 上的版本字段;如果两个编辑竞争则冲突 |
参见:references/building-blocks.md 了解聚合设计规则和一致性边界。
4. 领域事件
核心概念: 领域事件捕获了领域中发生的、专家关心的事情,以过去时态命名(OrderPlaced, PaymentReceived)——一个已经发生的事实。
为什么有效: 领域事件将原因与效果解耦。当 OrderPlaced 发布时,物流、计费和通知各自独立响应,而订单上下文不知道它们——更少的耦合、最终一致性、自然的审计跟踪。
关键见解:
- 事件是不可变的事实——一旦发布,不能更改或撤回
- 领域事件是限界上下文内部的;集成事件跨越边界
- 事件实现时间解耦:生产者不等待消费者
- 事件溯源将完整的事件历史存储为真相来源,通过重放推导当前状态
- 并非每个状态变化都值得一个事件——只发布领域关心的事件
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 状态转换 | 在领域动作上引发事件 | order.place() 引发 OrderPlaced |
| 跨上下文集成 | 发布集成事件 | OrderPlaced 在物流上下文中触发 ShippingLabelRequested |
| 最终一致性 | 异步事件处理器 | 库存处理器在 OrderPlaced 后异步更新库存 |
参见:references/domain-events.md 了解事件命名、事件溯源和集成事件。
5. 仓储和工厂
核心概念: 仓储提供领域对象的内存集合的假象,隐藏持久化。工厂封装复杂的创建逻辑,确保聚合始终以有效状态诞生。
为什么有效: 当持久化和组装细节泄漏到领域代码中时,每次存储更改都会波及业务规则,聚合可能以半有效状态构造。仓储将SQL/ORM关注点限制在基础设施中,使领域在内存中可测试;工厂使聚合的唯一路径强制其不变量,从而无法表示无效实例。
关键见解:
- 仓储接口属于领域层;其实现属于基础设施
- 仓储方法使用通用语言:
findPendingOrders(),而不是getByStatusCode(3) - 面向集合的仓储模拟
add/remove;面向持久化的使用save - 工厂适用于复杂规则或多部分组装;两个字段的值对象只需要构造函数
- 规约模式将查询条件封装为领域对象:
OverdueInvoiceSpecification
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 数据访问抽象 | 仓储接口 | 领域中的 OrderRepository.findByCustomer(customerId);基础设施中的 PostgresOrderRepository |
| 复杂创建 | 工厂方法 | Order.createFromQuote(quote) 从 Quote 聚合验证并组装 |
| 查询封装 | 规约 | spec = OverdueBy(days=30); repo.findMatching(spec) |
参见:references/repositories-factories.md 了解仓储、工厂和规约模式。
6. 战略设计与提炼
核心概念: 并非系统的所有部分都同等重要。战略设计识别核心领域——竞争优势所在——并将其与支撑性子领域(必要但不具区分性)和通用子领域(商品化)区分开来。
为什么有效: 对所有部分应用相同的严谨性会分散最优秀的人才,并对商品化功能过度工程化。识别核心领域将最优秀的开发者和最深入的建模集中在最重要的地方。
关键见解:
- 核心领域:投入最优秀的人才和最深入的建模;支撑性:构建但不过度工程化;通用(认证、邮件、支付):购买或使用开源
- 提炼从周围复杂性中提取并突出核心领域
- 领域愿景声明是对核心领域价值主张的一页描述
- 随着业务发展重新审视什么是“核心”——今天的差异化因素可能成为明天的商品
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 构建 vs. 购买 | 分类子领域类型 | 构建自定义定价引擎(核心);使用Stripe进行支付(通用) |
| 团队分配 | 最优秀开发者投入核心领域 | 高级工程师建模承保规则;初级工程师集成邮件服务 |
| 代码组织 | 将核心与通用分离 | domain/pricing/(深层模型) vs. infrastructure/email/(薄适配器) |
参见:references/strategic-design.md 在决定何处投入工程努力时使用——子领域分类和提炼技术。
常见错误
| 错误 | 失败原因 | 修复 |
|---|---|---|
| 技术名称而非领域语言 | 逻辑隐藏在 DataManager 后面;专家无法验证模型 |
重命名为领域术语(ClaimAdjudicator);如果没有领域术语,概念可能错误 |
| 一个模型统治一切 | 一个单一的 Customer 类用于计费、物流和营销变得臃肿且矛盾 |
限界上下文:每个上下文有自己的 Customer,只包含需要的属性 |
| 巨型聚合 | 并发冲突、加载缓慢、事务瓶颈 | 保持聚合小;通过ID引用;聚合之间最终一致性 |
| 贫血领域模型 | 对象是数据袋;规则分散在服务中并重复 | 将行为移入实体和值对象;服务仅编排 |
| 没有防腐层 | 外来模型泄漏;代码耦合到外部模式 | 将每个外部系统包装在转换层后面 |
| 限界上下文 = 微服务 | 过早提取;分布式复杂性没有好处 | 上下文是模型边界,不是部署单元;从单体中的模块开始 |
| 跳过领域专家 | 开发者发明了不匹配现实的模型;昂贵的返工 | 定期建模会话,直到专家说“是的,就是这样运作的” |
快速诊断
| 问题 | 如果否 | 行动 |
|---|---|---|
| 领域专家能阅读你的类名并理解它们吗? | 技术术语隐藏了模型 | 将类、方法、事件重命名为通用语言 |
| 限界上下文边界是否显式定义? | 模型渗透;同一术语含义不同 | 绘制上下文映射;定义边界和转换 |
| 聚合是否小(一个根+最小集群)? | 加载缓慢、并发问题 | 拆分聚合;通过ID引用;接受最终一致性 |
| 领域对象是否包含行为而不仅仅是数据? | 贫血模型;逻辑分散在服务中 | 将业务规则移入实体和值对象 |
| 是否使用领域事件进行跨聚合通信? | 紧耦合、同步链 | 引入事件;让聚合异步响应 |
| 每个外部集成是否有防腐层? | 外来模型污染你的领域 | 在每个边界添加转换层 |
| 是否已识别哪个子领域是核心? | 最优秀人才分散 | 分类子领域;将深入建模集中在核心领域 |
进一步阅读
有关完整方法论、模式和更深入的见解:
- 《领域驱动设计:软件核心复杂性应对之道》 by Eric Evans
关于作者
Eric Evans 是一位软件设计顾问,也是领域驱动设计的创始人,通过从事金融、保险和物流领域的大型系统开发而创立。他2003年的著作《领域驱动设计:软件核心复杂性应对之道》是有史以来最具影响力的软件架构书籍之一,他通过自己的咨询公司 Domain Language 继续发展DDD。






