领域驱动战略设计
# 06.领域驱动战略设计
# 目录介绍
- 1. 案例引入
- 2. 架构概览
- 3. 统一语言基础
- 4. 限界上下文识别
- 5. 子域分类与战略
- 6. 上下文映射九模式
- 7. 战略到战术衔接
- 8. 战略反模式
- 9. 事件风暴实战
- 10. 综合案例串讲
# 1. 案例引入
# 1.1 同名异义的混乱
先看一段真实事故。某中型 SaaS 公司,约 60 人技术团队,做的是面向中小企业的会员营销平台。系统跑了 3 年,业务跑通了,但每次跨团队开会都吵架——焦点全在一个词:「客户」。
场景:营销团队提需求:"给所有'高价值客户'发一张满 200 减 30 的券。"
开发反问:"'高价值客户'是指会员等级 ≥ V4 的吗?"
营销:"不是,是指最近 90 天消费 ≥ 5000 元的。"
CRM 团队插话:"等等,我们 CRM 里说的'高价值客户'是指 LTV(生命周期价值)大于 1 万的,跟你们都不一样。"
财务:"我这边'客户'是指开过发票的法人主体,自然人不算。"
客服:"我们工单里的'客户'是来电号码主人,连账号都不一定有……"
一个会开下来,「客户」这个词在五个团队嘴里指五种不同的东西。最后会议纪要是这么写的:"给高价值会员发券"——开发回去问产品:"你这'高价值'按谁的标准?" 产品说:"你们定吧。" 开发只好按"会员等级 ≥ V4"实现了——上线两天后被营销总监投诉,优惠券发错了人,CEO 拍桌子要追责。
继续往下挖,发现这并不是个例:
系统里到处都有「同名异义」与「异名同义」:
- "订单"在交易系统 = 包含商品和支付的复杂对象
- "订单"在物流系统 = 一个运单(含重量、目的地)
- "订单"在 CRM = 一次成交记录(只有金额和时间)
- "用户"在登录系统 = 凭证(账号 + 密码)
- "用户"在会员系统 = 等级权益对象(含积分、生日、偏好)
- "买家"和"客户"和"会员"和"用户" —— 业务方分得清,系统里全混着叫
2
3
4
5
6
7
直觉怀疑:是不是字典/术语表没维护好?打开公司 Wiki 一看,确实有一份 2 年前写的「术语规范」,但已经没人维护,新概念全靠口耳相传——更糟的是,术语表写的「客户 = 注册过的用户」根本和五个团队任何一个都对不上。
# 1.2 顺藤摸到根因
带着这条线往下挖:
- 假设 1:是不是产品文档不清楚?—— 翻开最新需求文档,写的就是"针对客户进行精准营销",业务方自己也没区分。
- 假设 2:是不是开发命名水平差?—— 看代码,类名是
Customer、User、Member、Buyer四个并存,每个都被多处使用且互相调用。 - 假设 3:要不强制统一成一个
Customer类?—— DBA 试过:把所有相关表合并成一张customer表,结果字段涨到 87 个,营销加字段必须协调登录团队上线,最后退回。 - 假设 4:那要不每个团队完全用自己的概念?—— 也试过:交易出"订单",CRM 接到时不知道里面字段叫什么,集成全靠口头约定,接口出 bug 排查要 3 天。
- 假设 5:根因到底在哪?——业务上"客户"在不同场景本来就是不同的东西,但系统把它们要么强行合并、要么完全割裂,没有"翻译规则"。
看似"命名问题"的事故,毛病不在词不在表,毛病在没有意识到——业务概念是有边界的,边界内统一、边界外翻译。这条不是 OOP 设计问题,是领域驱动战略设计的根问题。
这一段事故里至少藏着 7 个原理点:
① 为什么"全公司一个统一模型"行不通? → 第 8.1 章
② 业务概念的边界怎么找? 凭什么说"这里是边界"? → 第 4 章
③ 不同上下文里同名概念怎么共存又不打架? → 第 6 章上下文映射
④ 哪些子域值得砸钱自研, 哪些买就行? → 第 5 章
⑤ 业务方和开发为什么经常"说同一个词不是一回事"? → 第 3 章统一语言
⑥ 战略画完了, 到代码层怎么落? → 第 7 章
⑦ 怎么把全公司业务模型一次理清? → 第 9 章事件风暴
2
3
4
5
6
7
# 1.3 我们要回答什么
这个事故就是本篇的主线案例。我们带着上面 7 个问号往下走,每讲完一章就解开一两个;最后在第 10 章把案例彻底剖开,并给出三种修复方案与各自的代价。
本篇路线:
战略设计五要素总图 (第 2 章)
↓
统一语言 (第 3 章) ─→ 解开"为什么业务和开发对不上"
↓
限界上下文 → 子域分类 (第 4-5 章) ─→ 解开"边界在哪、为什么"
↓
上下文映射 (第 6 章) ─→ 解开"跨边界如何协作"
↓
战略到战术 → 反模式 (第 7-8 章) ─→ 解开"如何落地与避坑"
↓
事件风暴实战 (第 9 章) ─→ 武器库
↓
综合案例 (第 10 章) ─→ 案例彻底剖开
2
3
4
5
6
7
8
9
10
11
12
13
📌 本篇定位:在系列中,本篇是承上启下的方法论核心。前面 02.六边形架构、03.CQRS、04.事件驱动、05.微服务拆分 都依赖"边界已经划好"这个前提;本篇就是讲这条边界从哪来。读完本篇后再看微服务/CQRS/事件驱动,能立刻回答:"这个边界凭什么这么划"。
# 2. 架构概览
# 2.1 战略设计五要素
DDD 战略设计是 Eric Evans 在 2003 年《领域驱动设计》一书中提出的一组思维工具,专门解决"业务复杂度如何拆解"。整套方法围绕五个核心要素:
┌─────────────────────────────────────────────────────────┐
│ ① 领域 (Domain) │
│ 一个组织在做的事情整体 │
│ 如:电商、保险、教育 │
├─────────────────────────────────────────────────────────┤
│ ② 子域 (Subdomain) │
│ 领域内按业务能力的切分 │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │核心子域│ │支撑子域│ │通用子域│ │
│ │ 交易 │ │ 营销 │ │ 认证 │ │
│ └────────┘ └────────┘ └────────┘ │
├─────────────────────────────────────────────────────────┤
│ ③ 限界上下文 (Bounded Context) │
│ 模型边界 + 通用语言边界 + 团队边界 (三位一体) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 交易上下文 │ │ 营销上下文 │ │ 会员上下文 │ │
│ │ Order/Item │ │ Coupon/Rule │ │ Member/Lv │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ ④ 通用语言 (Ubiquitous Language) │
│ 每个上下文内业务和开发共用的同一套词 │
│ "订单"="交易上下文内的 Order 聚合" │
├─────────────────────────────────────────────────────────┤
│ ⑤ 上下文映射 (Context Map) │
│ 上下文之间的协作关系图谱(9 种模式) │
│ 交易 ──发布事件──→ 营销 (PL) │
│ 交易 ──RPC──→ 会员 (ACL 防腐层) │
└─────────────────────────────────────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
五要素的核心属性速查:
| 要素 | 定义 | 关键属性 | 输出物 |
|---|---|---|---|
| 领域 | 公司在做的业务整体 | 唯一、不变 | 一句话使命 |
| 子域 | 按业务能力分块 | 分核心/支撑/通用三级 | 子域地图 |
| 限界上下文 | 模型边界 = 团队边界 | 内部语言一致,跨界要翻译 | 上下文列表 |
| 通用语言 | 业务-开发共识词典 | 上下文内唯一 | 术语表 + 代码命名 |
| 上下文映射 | 上下文协作关系 | 9 种模式可选 | 上下文映射图 |
# 2.2 为什么这么切
为什么 DDD 战略要分成"领域 → 子域 → 上下文 → 语言 → 映射"五层,而不是直接"画几个服务"完事?
疑惑:直接拿白板画几个框、连几条线,写明"这个服务负责什么",不就够了?为什么要五层概念?
论证:
概念错位是最贵的错误——代码 bug 几小时就能修,但模型错了,一年的代码全要推倒重来。先有清晰的领域思维,再写代码,是把"贵的错误"前置到便宜阶段。
业务复杂度天然是分层的——一个公司做的事(领域)天然由若干能力组成(子域),每种能力天然有自己的概念体系(上下文),每个上下文内业务和开发天然要说同一种话(语言),上下文之间天然要协作(映射)。五要素只是把这种天然结构显式化。
不同抽象层服务不同决策——
- 领域层:决定"我们是谁、做什么"
- 子域层:决定"哪些事自研、哪些买"
- 上下文层:决定"团队怎么分、服务怎么拆"
- 语言层:决定"代码里怎么命名"
- 映射层:决定"跨团队接口如何协作"
每一层错了,影响的决策范围不同——领域错了 = 战略错;上下文错了 = 组织错;语言错了 = 沟通错。
可演化才有意义——业务每年都在变,一次性"画死"的架构图三个月就过时。五要素是一套思考框架,让架构图能跟着业务变——子域可以重分类、上下文边界可以调整、语言可以演化。
反向验证——不用 DDD 战略的项目会怎样?参考第 1 章案例:业务方一个"客户"五种含义,开发胡乱建模,最后变成"数据库表为中心"的反向架构——表怎么设计代码怎么写,没人在乎业务语义,一年后系统僵化、bug 频出。
结论:DDD 战略设计的五要素,本质上是把"业务复杂度"的拆解过程标准化——领域定边界、子域定优先级、上下文定团队、语言定共识、映射定协作。这是面对中大型业务系统的思维上的根基哲学。
下面我们从最基础的"统一语言"开始,看它如何决定后续的一切。
# 3. 统一语言基础
# 3.1 语言即模型
疑惑:业务方说"客户"、开发写 User、产品文档写"会员"——只是用词不同而已,沟通时翻译一下不就行?
论证:试试看实际后果:
业务方: 开发翻译: 代码实现:
"高价值客户" → "VIP用户" → user.isVip()
"流失客户" → "30天没登录" → user.lastLoginAt < now-30d
"潜在客户" → "未注册访客" → 没有 User 对象,存在 Visitor 表
2
3
4
三次"翻译"后,业务方再问:"给所有高价值客户发券" → 开发去找 user.isVip()——但业务说的"高价值"和代码里的"VIP" 早就不是一个东西了。VIP 标记半年前因为某次活动加上的,从此谁都不敢动;业务方说的"高价值"实际是近期消费额。两者重叠率不到 30%。
这就是翻译损耗——每翻译一次,就丢失一点语义;翻译三次,业务概念已经被磨损得面目全非。
结论:语言即模型,模型即代码。业务方说的词、产品文档写的词、代码里类名/方法名/字段名,必须是同一个词——这叫"统一语言(Ubiquitous Language)"。
❌ 翻译模型 ✅ 统一语言
业务: 高价值客户 业务: 高价值客户
↓ 翻译 ↓ 不翻译
产品: VIP 产品: 高价值客户
↓ 翻译 ↓ 不翻译
开发: User.isVip() 开发: Customer.isHighValue()
↓ 翻译 ↓ 不翻译
DB: user.vip_flag DB: customer.is_high_value
2
3
4
5
6
7
8
统一语言不是"开发去学业务术语"或"业务去学技术术语",而是业务和开发一起重新定义一套双方都用的词——往往这个过程就会暴露很多业务上的歧义("高价值"到底是什么意思?)。
# 3.2 翻译损耗的代价
翻译的代价是数量级递增的。一次需求评审到上线的完整链路:
业务方头脑里的概念 (信噪比 100%)
│翻译 1
▼
口述需求 / 微信讨论 (信噪比 90%)
│翻译 2
▼
PRD 文档 (信噪比 80%)
│翻译 3
▼
开发理解后的设计文档 (信噪比 65%)
│翻译 4
▼
数据库表设计 + 类设计 (信噪比 50%)
│翻译 5
▼
代码实现 (信噪比 35%)
│翻译 6(业务方上线后理解代码行为)
▼
业务方最终感知 (信噪比 20%)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
每次翻译损失 10~20% 的语义——六层下来只剩 1/5。这是大型项目"业务和开发永远在打架"的根因。
统一语言的本质就是砍掉中间所有翻译层:
业务方头脑里的概念
│
└─── (同一个词、同一个定义、同一个模型) ───┐
│
▼
所有人(产品/开发/测试/运维/客服) 都用同一个词
│
▼
代码里也是同一个词
2
3
4
5
6
7
8
9
信噪比从 35% 拉回到 90%+。这就是 DDD 战略的第一红利。
# 3.3 沉淀语言的方法
"统一"不是嘴上说说,要有沉淀方法。三个落地动作:
方法 1 · 术语表(Glossary)
每个上下文一份术语表,业务和开发共同维护:
# 营销上下文术语表 v2.1(最后更新 2025-06-01)
## 客户类
- **高价值客户(High-Value Customer)**:最近 90 天累计消费 ≥ 5000 元的注册客户。
- 同义词:(业务方常说)"大客户"、"重点客户" — 统一用"高价值客户"
- 反义词:普通客户、潜在客户
- 计算口径:以订单完成时间为准(不含取消单)
- 代码:`Customer.isHighValue()`
## 优惠券类
- **满减券(Discount Voucher)**:达到指定消费门槛后立减固定金额的优惠凭证。
- 不要混用:"折扣券"、"代金券"(折扣券指打折,代金券指无门槛)
- 代码:`DiscountVoucher`
- DB:`voucher`(type='DISCOUNT')
2
3
4
5
6
7
8
9
10
11
12
13
14
方法 2 · 代码即语言
代码里的类名、方法名、字段名、变量名必须与术语表完全一致,禁止译名变体:
// ❌ 名词不一致
class User {
boolean isVip() { ... } // 业务说"高价值客户"
}
// ✅ 与术语表一致
class Customer {
boolean isHighValue() { ... } // 业务用什么词,代码就用什么词
}
2
3
4
5
6
7
8
9
方法 3 · 文档即代码
PRD、设计文档、接口文档统一从术语表引用。写新文档时如果出现新词,先回去更新术语表——任何"自由发挥"的新词都视为债务。
# 3.4 语言演化与守护
语言是会变的——业务变化、概念精化都会让语言进化。守护三纪律:
纪律 1 · 增量更新,不要积压
新概念出现时立刻入表,不要等季度复盘批量更新——一周不更新,新词就开始流传变体。
纪律 2 · 谁提概念谁负责定义
业务方提的新需求里有新名词,评审时必须给定义——"你说的'活跃用户'是什么意思?"应作为评审环节的标准动作。
纪律 3 · 定期 review,删除过期词
每季度一次术语表 review,下线不再使用的旧词——保留只会让新人困惑。删除的词归档到"历史术语"。
红线:
| 红线 | 后果 |
|---|---|
| 业务方在群里说新词没人挑战 | 半年后变成跨团队歧义源 |
| 代码评审通过了非术语表里的命名 | 一年后变成无人能读的祖传代码 |
| 术语表更新不通知相关团队 | 同一个词两种解释 → 第 1 章案例重演 |
| 跨上下文偷用对方的术语 | 边界模糊 → 退化成"全公司统一模型" |
统一语言不是一份文档,是一套日常约束——评审、命名、文档三处同时守,才能不退化。
# 4. 限界上下文识别
# 4.1 上下文的本质
疑惑:第 3 章说"上下文内统一"——这"上下文"到底是什么?怎么知道一段业务在哪个上下文?
论证:先看定义:
限界上下文(Bounded Context)= 一个模型 + 一套通用语言 + 一个团队 + 一组明确的边界
四位一体,缺一不可:
┌──────────────────┐
│ 限界上下文 │
│ │
模型边界 │ Customer │ 通用语言边界
┌─────────┤ Order ├─────────┐
│ │ Coupon │ │
│ │ │ │
│ └──────────────────┘ │
│ │
└─── 团队边界 ─── 部署边界 ─── 数据库边界 ─┘
2
3
4
5
6
7
8
9
10
为什么这四个边界天然重合?
- 模型边界:在这个上下文里,"客户"指的是 X;出了边界,"客户"可能指 Y。模型形状不能跨越。
- 通用语言边界:上下文内业务-开发说同一种话;跨边界要"翻译"。语言不能跨越。
- 团队边界:模型由谁维护,语言由谁定义,只能由同一个团队负责——多团队同时改一个上下文 = 模型必然碎裂。
- 部署/数据库边界:上下文是独立演进的单位——独立发布、独立扩容、独立数据存储(参见第 7 章03.CQRS 一库一服务原则)。
典型电商的上下文划分:
┌─────────────────────────────────────────────────────────┐
│ 核心子域 │
│ ┌───────────────┐ ┌───────────────┐ │
│ │ 交易上下文 │ │ 商品上下文 │ │
│ │ Order/Payment │ │ Product/SKU │ │
│ └───────────────┘ └───────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 支撑子域 │
│ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ 营销上下文 │ │ 会员上下文 │ │ 物流上下文 │ │
│ │ Coupon/Promo │ │ Member/Level │ │ Shipment │ │
│ └───────────────┘ └───────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 通用子域 │
│ ┌───────────────┐ ┌───────────────┐ ┌─────────────┐ │
│ │ 认证上下文 │ │ 通知上下文 │ │ 支付上下文 │ │
│ │ Account/Token │ │ SMS/Email │ │ 三方对接 │ │
│ └───────────────┘ └───────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
第 1 章案例中的"客户"在不同上下文里是不同的东西:
| 上下文 | "客户" 的含义 | 关键属性 |
|---|---|---|
| 认证上下文 | Account(账号凭证) | account、password、token |
| 会员上下文 | Member(会员档案) | level、points、joinDate |
| 营销上下文 | Customer(营销对象) | tags、rfm、segments |
| 交易上下文 | Buyer(买家) | shippingAddress、paymentMethod |
| 客服上下文 | Complainant(投诉人) | contact、ticketHistory |
这五个"客户"本质上不是同一个东西——只是恰好都用了"客户"这两个字。强行合并就是第 1 章 87 字段大表的悲剧。
# 4.2 同名异义识别法
如何系统化识别上下文边界?同名异义识别法是最直接的工具。
操作步骤:
第 1 步:把全公司所有名词列出来
- 来源:业务文档、代码、Wiki、需求文档、表名
- 用便利贴或表格
- 不去重
第 2 步:找"同名"的词
- 相同的词出现在多个地方
- 标红:高频疑似同名异义词
第 3 步:逐个询问"在不同地方含义一样吗"
- 一样 → 这是真的同义概念
- 不一样 → 这是上下文边界信号
第 4 步:把"不一样"的概念按"业务场景"聚类
- 同场景的概念聚在一起 → 一个上下文的候选
2
3
4
5
6
7
8
9
10
11
12
13
14
15
第 1 章案例的实操:
| 词 | 出现场景 | 含义 | 是否同义 |
|---|---|---|---|
| 客户 | 登录 | 账号凭证 | ❌ 不同义 |
| 客户 | 会员 | 等级权益对象 | ❌ 不同义 |
| 客户 | 营销 | 标签分群对象 | ❌ 不同义 |
| 客户 | 交易 | 买家 | ❌ 不同义 |
| 客户 | 财务 | 法人主体 | ❌ 不同义 |
| 客户 | 客服 | 投诉人 | ❌ 不同义 |
5 处出现 5 种含义 → 强信号:这 5 处对应 5 个上下文。
异名同义反向识别:
| 词 | 实际场景 | 是否同义 |
|---|---|---|
| 买家 / 用户 / 客户 | 都指"下单的人"在交易里 | ✅ 同义 → 应该选一个标准词 |
| 优惠券 / 代金券 / 折扣码 | 业务方混着叫 | ⚠️ 需要业务方明确是不是同一回事 |
# 4.3 边界判定四标尺
光靠"同名异义"不够,需要更系统的判定标准。边界判定四标尺:
| 标尺 | 边界信号(应该拆) | 合并信号(应该合) |
|---|---|---|
| 语言 | 同一个词,含义不同 | 词汇高度重叠 |
| 规则 | 业务规则完全不同(如"下架"逻辑) | 规则共享 |
| 生命周期 | 数据创建/变更/销毁的时机不同 | 同生共死 |
| 团队 | 不同团队的核心需求 | 同一团队主导 |
四标尺打分法:
对任意两个候选概念(如"商品"和"库存")打分:
- 语言相同?(0 = 完全不同 / 5 = 完全相同)
- 规则相同?(0~5)
- 生命周期相同?(0~5)
- 团队相同?(0~5)
总分 ≥ 15 → 强烈建议同上下文
总分 10~14 → 中等耦合,看具体场景
总分 ≤ 9 → 应该是不同上下文
2
3
4
5
6
7
8
9
举例:
"商品" vs "库存":
- 语言:商品维度(SPU/SKU)vs 库存维度(仓位/批次) → 2
- 规则:商品上下架 vs 库存盘点 → 1
- 生命周期:商品长期存在 vs 库存频繁变动 → 1
- 团队:商品运营 vs 仓储 → 1
总分 5 → 不同上下文(商品上下文 + 库存上下文)
"订单" vs "支付":
- 语言:订单/支付订单术语接近 → 4
- 规则:下单和支付几乎是同一业务流程 → 4
- 生命周期:订单创建→支付→完成强耦合 → 5
- 团队:交易团队同时管 → 5
总分 18 → 同上下文(交易上下文)
2
3
4
5
6
7
8
9
10
11
12
13
# 4.4 上下文与团队映射
康威定律的反向应用:
系统设计反映组织结构 → 组织结构必须与上下文对齐。
上下文 ↔ 团队的对应原则:
✅ 1对1:一个上下文 = 一个团队
优点:边界清晰,决策快
适用:成熟的核心/支撑上下文
⚠️ N对1:一个团队管多个上下文
适用:团队规模允许,且上下文相对简单
风险:团队精力分散,可能照顾不过来
❌ 1对N:一个上下文由多个团队共同维护
后果:模型必然碎裂、语言必然分裂
除非:两团队是临时共建关系,且约定 6 个月内归并
2
3
4
5
6
7
8
9
10
11
第 1 章案例的组织反映:
原状(错位): 目标(对齐):
┌─ 交易团队 ─ 交易上下文
后端组(20人) ── 维护所有"客户" ──┐ 后端组拆分 ─┼─ 营销团队 ─ 营销上下文
│ ├─ 会员团队 ─ 会员上下文
│ └─ 平台团队 ─ 认证/通知
营销组、CRM组、客服组各自 │
对"客户"提需求,后端选一种实现 │ 每个团队完全负责自己的上下文,
│ 对外通过明确契约协作
▼
歧义爆炸
2
3
4
5
6
7
8
9
10
实操判定:
- 如果一个团队同时被 3 个以上业务方"提需求"——大概率上下文没划清
- 如果一个上下文要找 3 个以上团队"会签"才能改——大概率边界不在团队上
- 如果一个团队的人在内部分成几个小组各管一摊——可能上下文太大该拆
# 5. 子域分类与战略
# 5.1 核心子域的识别
疑惑:识别完上下文,每个都同等重要吗?应该平均投入资源吗?
论证:绝对不平均。子域分三级,投入策略完全不同:
战略价值
▲
│
核心子域 ★★★★★ ── 公司差异化竞争力
│
支撑子域 ★★★ ── 业务必须但不差异化
│
通用子域 ★ ── 行业通用、可外购
│
└─────────────► 投入策略
核心:重金自研
支撑:适度投入
通用:买/外包
2
3
4
5
6
7
8
9
10
11
12
13
核心子域识别四问:
- 如果它不存在公司还是公司吗?→ 是 → 候选核心子域
- 它是公司比竞争对手强的地方吗?→ 是 → 候选核心子域
- 它是 CEO 在投资人面前讲的核心故事吗?→ 是 → 候选核心子域
- 行业里有标准产品可以买吗?→ 没有 → 候选核心子域
四问全 ✓,几乎确定是核心子域。
典型例子:
| 公司 | 核心子域 | 理由 |
|---|---|---|
| 淘宝 | 商品搜索、推荐 | 千人千面是平台核心竞争力 |
| 美团 | 订单调度、骑手路径 | 配送效率决定生死 |
| 字节 | 推荐算法 | 算法即护城河 |
| 银行 | 风控、信贷模型 | 风险定价是核心能力 |
| 第 1 章案例 SaaS | 营销精准触达、自动化运营 | 这就是产品卖点 |
# 5.2 支撑子域的取舍
支撑子域:业务必须有,但不直接产生差异化竞争力——做得好不会让公司更强,但做不好会让公司挂掉。
典型例子:
| 子域 | 为什么是支撑而非核心 |
|---|---|
| 会员等级体系 | 行业有标准玩法,差异不大 |
| 营销活动后台 | 工具属性,配置型 |
| 订单履约状态机 | 流程标准,无算法 |
| 报表分析 | 内部使用,不对外卖 |
| 客服工单 | 标准 SaaS 能覆盖 |
投入策略:
支撑子域的三个选择:
A. 适度自研 (推荐)
- 资深团队搭骨架,普通团队维护
- 用成熟开源框架/组件
- 不追求极致性能/体验
- 投入:核心子域的 30~50%
B. 买现成 + 二开
- 找成熟 SaaS(如 Salesforce CRM)
- 自己写适配层、做必要二开
- 投入低,启动快
- 但有锁定风险
C. 完全外包
- 适合一次性建设、后续不大改的
- 注意外包合同里"代码所有权"条款
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
红线:绝不把支撑子域当核心子域投入——把"会员等级"做成自研 RPA 平台 = 浪费三年人力。
# 5.3 通用子域的复用
通用子域:全行业通用,不投入也得有——身份认证、消息推送、文件存储、支付通道。
典型例子与建议方案:
| 通用子域 | 推荐方案 |
|---|---|
| 用户登录认证 | 用 OAuth2 / OIDC 标准协议 + 开源/云服务(如 Keycloak、Auth0) |
| 短信发送 | 用云服务(阿里云/腾讯云 SMS) |
| 邮件发送 | 用 SendGrid / 阿里云邮件推送 |
| 对象存储 | 用 OSS / S3 / COS |
| 支付通道 | 用支付宝 / 微信支付 / Stripe |
| 地图服务 | 用高德 / 百度 / Google Maps |
| 实时通信 | 用声网 / 即构 / Twilio |
核心纪律:通用子域绝不自研——你不可能比阿里云做出更好的短信通道,也不应该花团队精力去做。
例外情况:
什么时候通用子域要"自研"?
- 业务量大到买不起 (如日发短信千万级,自建可能省钱)
- 有合规/数据主权要求 (如金融机构自建支付通道)
- 该子域恰好是公司核心 (如阿里云本身就是做云服务的)
2
3
4
# 5.4 投资决策矩阵
子域投资矩阵——把战略价值和实现难度交叉,给出投资决策:
实现难度
低 高
┌────────────┬────────────┐
│ │ │
高 │ 快速建 │ 集中力量 │
│ (买/简) │ (自研) │
战略 │ │ │
价值 ├────────────┼────────────┤
│ │ │
低 │ 最小可用 │ 果断外包 │
│ (开源凑) │ (买现成) │
│ │ │
└────────────┴────────────┘
→ 子域定位决定投资额,投资额决定团队配置
2
3
4
5
6
7
8
9
10
11
12
13
14
15
具体到投资额(中型公司参考):
| 子域类型 | 团队规模 | 年预算占比 | 技术债务容忍度 |
|---|---|---|---|
| 核心子域 | 团队总人力的 40~60% | 50%+ | 极低(重点重构) |
| 支撑子域 | 30~40% | 30% | 中等 |
| 通用子域 | 5~10%(含运维) | 20%(多数是采购费) | 高(能跑就行) |
第 1 章案例的诊断:
原状(错配):
- 70% 人力在做"会员等级体系"重写 → 这是支撑子域
- 20% 人力在做"自建短信网关" → 这是通用子域
- 10% 人力在做"营销精准触达" → 这才是核心子域
结果:
- 核心子域投入不足 → 营销效果差,客户流失
- 支撑子域过度投入 → 复杂度高、bug 多
- 通用子域自研 → 不如直接买阿里云便宜
修复:
- 营销精准触达 → 扩到 50%(核心子域应得)
- 会员等级 → 缩到 30%(用成熟模型即可)
- 短信网关 → 砍到 5%,迁阿里云 → 省 60% 成本
2
3
4
5
6
7
8
9
10
11
12
13
14
核心原则:资源永远稀缺,要砸在核心子域上。把核心子域当通用子域对待 = 自废武功;把通用子域当核心子域对待 = 资源浪费。两种错配都会让公司输给竞争对手。
# 6. 上下文映射九模式
# 6.1 合作与共享内核
疑惑:上下文划完了,但业务必须协作——A 上下文要用 B 上下文的数据,怎么办?
论证:DDD 总结了 9 种上下文映射模式,每种描述一种典型协作关系:
┌─────────────────────────────────────────────────────────┐
│ 9 种上下文映射模式 │
├─────────────────────────────────────────────────────────┤
│ 1. Partnership 合作关系 │
│ 2. Shared Kernel 共享内核 │
│ 3. Customer/Supplier 客户-供应商 │
│ 4. Conformist 追随者 │
│ 5. Anti-Corruption Layer 防腐层 (ACL) ★最常用 │
│ 6. Open Host Service 开放主机服务 (OHS) │
│ 7. Published Language 发布语言 (PL) │
│ 8. Separate Ways 分离方式 │
│ 9. Big Ball of Mud 大泥球 (反模式) │
└─────────────────────────────────────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
13
模式 1 · Partnership(合作关系)
两个上下文深度绑定、协同开发——任何一方接口变更必须双方一起评审。
上下文 A ◄═════════► 上下文 B
│ 共同演化 │
│ 共同发布 │
└───── 共担成败 ─────┘
适用:业务深度耦合,必须一起成功或一起失败的两个核心上下文
风险:耦合过紧,本质上是"两个上下文不该分开"
2
3
4
5
6
7
典型例子:电商的"商品上下文"和"价格上下文"——商品改规格往往要同步价格策略,反之亦然。
模式 2 · Shared Kernel(共享内核)
两个上下文共享一小段模型或代码——这部分由双方共同维护。
┌──────────────┐ ┌──────────────┐
│ 上下文 A │ │ 上下文 B │
│ │ │ │
│ ┌──────────┴──────┬──┘ │
│ │ 共享内核 │ │
│ │ Money/Currency │ │
│ └──────────┬──────┘ │
│ │ │ │
└──────────────┘ └──────────────┘
适用:极少数真正稳定的公共概念(如 Money、Address)
警告:内核只能放"语义级稳定"的小模型,绝不能放业务逻辑
2
3
4
5
6
7
8
9
10
11
12
红线:共享内核只能装结构稳定的值对象——Money、DateRange、Address。禁止放业务规则、聚合根、领域服务——一旦放进去就意味着两个上下文必须共同演进。
# 6.2 客户供应商关系
模式 3 · Customer/Supplier(客户-供应商)
下游是"客户",上游是"供应商"——下游需求能影响上游优先级。
上游 (供应商) 下游 (客户)
会员上下文 ────────────► 营销上下文
│
│ "营销需要新字段:lastPurchaseDate"
▼
会员团队 ◄──── 排期满足 ── 营销团队
适用:内部团队之间,下游对上游有正式影响力的合作
要求:有明确的需求池、迭代节奏、SLA
2
3
4
5
6
7
8
9
关键特征:下游不是"被动接受",而是有发言权——上游会把下游需求纳入迭代规划。
模式 4 · Conformist(追随者)
下游完全适配上游模型,不反抗、不翻译。
上游 (强势) 下游 (弱势)
外部支付系统 ────────────► 我方订单上下文
(字段名 USR_NO/CST_ID) (代码里也写 USR_NO/CST_ID)
适用:上游是外部系统、大厂、合作方,你无法影响它
代价:上游模型再丑,你也得用;上游字段语义变,你也跟着痛
2
3
4
5
6
何时选 Conformist:当 ACL 的翻译成本 > 模型不优雅的代价时——比如对接一个用一年就下线的合作方系统。
# 6.3 防腐层与开放主机
模式 5 · Anti-Corruption Layer(防腐层,ACL)⭐ 最常用
下游建一层翻译适配层,把上游模型转换成本上下文需要的模型。
本上下文 (干净) ACL (翻译层) 外部上下文 (脏)
┌──────────────┐ ┌────────────┐ ┌───────────────────┐
│ │ │ Translator │ │ legacy_user.dat │
│ Customer │◄───┤ Adapter │◄─────│ USR_NO │
│ - id │ │ Mapper │ │ USR_NM_CN │
│ - name │ │ │ │ GRD_LV │
│ - level │ └────────────┘ │ RGSTR_DT │
│ │ 一层防腐墙 │ ... │
└──────────────┘ └───────────────────┘
本侧模型简洁优雅 外部模型奇葩难懂
红利:
- 外部模型再烂,本上下文不被污染
- 外部接口变更,只改 ACL 一个地方
- 本侧团队心智负担最小
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ACL 的实现:
// 本上下文的领域模型
class Customer {
CustomerId id;
String name;
MemberLevel level;
}
// ACL 层(防腐层)
class LegacyUserAcl {
private LegacyUserClient legacyClient;
public Customer fetchCustomer(CustomerId id) {
LegacyUserDTO raw = legacyClient.queryByUsrNo(id.value());
// 翻译:奇葩字段 → 本侧领域语言
return new Customer(
new CustomerId(raw.getUSR_NO()),
raw.getUSR_NM_CN(),
mapLevel(raw.getGRD_LV()) // GRD_LV='A' → MemberLevel.PLATINUM
);
}
private MemberLevel mapLevel(String legacyLevel) {
return switch (legacyLevel) {
case "A" -> MemberLevel.PLATINUM;
case "B" -> MemberLevel.GOLD;
default -> MemberLevel.NORMAL;
};
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
铁律:任何对接老旧系统、外部系统、第三方 API 的场景,默认建 ACL——成本永远比"侵入式适配"低。
模式 6 · Open Host Service(开放主机服务,OHS)
上游为多个下游提供一套稳定、标准化的开放接口。
┌─── 下游 A
上游 ──OHS──┼─── 下游 B
├─── 下游 C
└─── 下游 D
要求:
- 接口契约明确(OpenAPI / gRPC Proto)
- 严格向后兼容
- 多版本并存
- 完善文档
适用:核心子域上游被多个下游消费(如订单服务被营销/履约/客服共同使用)
2
3
4
5
6
7
8
9
10
11
12
OHS vs Customer/Supplier:
- C/S:下游数量少(1~2 个),可以为下游定制
- OHS:下游数量多(5+),必须标准化、不定制
# 6.4 发布语言与分离
模式 7 · Published Language(发布语言,PL)
上下游之间约定标准化的协议/格式——独立于任何一方实现。
上下文 A ──┐ ┌── 上下文 C
│ Published Language │
├─── (JSON Schema / IDL) ────┤
│ │
上下文 B ──┘ └── 上下文 D
例子:
- 行业标准:HL7(医疗)、SWIFT(银行)、ISO20022(金融)
- 公司标准:自定义 Event Schema(如 OrderCreatedEvent v1.0)
- 通用标准:CloudEvents、OpenTelemetry
2
3
4
5
6
7
8
9
10
与 OHS 关系:OHS 是"开放接口",PL 是"接口使用的语言/格式"——常一起出现:上游提供 OHS,OHS 用 PL 描述。
模式 8 · Separate Ways(分离方式)
两个上下文完全不集成——各做各的,没有任何接口往来。
上下文 A 上下文 B
│ │
│ (无任何关系) │
│ │
适用:集成成本 > 收益时
例子:
- HR 系统 vs 营销系统:业务上几乎无关
- 内部工具 vs 客户产品:客户根本看不到内部
2
3
4
5
6
7
8
9
警惕:Separate Ways 不是"忘了集成",是"主动选择不集成"——必须明确写在上下文映射图上,否则未来会有人"补一个 RPC"。
模式 9 · Big Ball of Mud(大泥球)⚠️ 反模式
上下文边界混乱、互相耦合、谁也说不清谁调谁。
A ─→ B ─→ C
↑ ↘ ↙ ↘ ↗ │
│ ╳ ╳ │
↓ ↗ ↘ ↗ ↘ ↓
D ←─ E ←─ F
特征:
- 修改任何一处影响多处
- 模型互相穿透,谁的代码都在谁里面
- 上下文形同虚设
2
3
4
5
6
7
8
9
10
修复方法:老老实实做边界手术——按本章方法重新画图,逐个建 ACL 隔离,分阶段切流。详见 05.微服务拆分策略 第 8 章。
九模式选型决策表:
| 场景 | 推荐模式 |
|---|---|
| 两个核心上下文,团队紧密协作 | Partnership |
| 共享极小段稳定模型 | Shared Kernel(慎用) |
| 内部上下游,下游有发言权 | Customer/Supplier |
| 对接强势外部系统 | Conformist |
| 对接老旧/外部/不稳定系统 | ★ 防腐层 (ACL) |
| 核心上游被多下游消费 | OHS + PL |
| 跨组织、跨公司协议 | Published Language |
| 业务上无关,强行集成无价值 | Separate Ways |
| 当前混乱状态,要重构 | 识别并重画为以上之一 |
# 7. 战略到战术衔接
# 7.1 上下文内的战术建模
战略设计画完了——领域、子域、上下文、语言、映射——下一步进入上下文内的战术建模。
战略设计 (本篇) 战术设计 (在面向对象专栏)
[实体/值对象/聚合/领域服务/资源库/事件]
限界上下文 │
│ │
└─── 上下文内部 ────────────┘
就是战术建模的舞台
2
3
4
5
6
每个限界上下文内部,独立做战术建模:
交易上下文 会员上下文
┌─────────────────────────┐ ┌─────────────────────────┐
│ 聚合根: Order │ │ 聚合根: Member │
│ - OrderItem (实体) │ │ - Level (值对象) │
│ - Money (值对象) │ │ - Points (值对象) │
│ 领域服务: PriceCalculator│ │ 领域服务: LevelUpgrade │
│ 资源库: OrderRepository │ │ 资源库: MemberRepository │
│ 事件: OrderPlaced │ │ 事件: LevelUpgraded │
└─────────────────────────┘ └─────────────────────────┘
战术建模在内部进行 战术建模在内部进行
每个上下文独立 每个上下文独立
2
3
4
5
6
7
8
9
10
11
核心纪律:战术建模严禁跨上下文——交易上下文 的 Order 永远不能直接持有 会员上下文 的 Member 对象引用,只能持有 BuyerId 这种 ID 值对象,需要 Member 详情时通过 ACL 或事件获取。
# 7.2 聚合根边界落地
聚合根(Aggregate Root) 是战术设计核心——但它的边界由战略设计决定。
战略层决定:
- 哪些概念在同一上下文 (第 4 章四标尺)
- 哪些是聚合根 (业务一致性边界)
战术层执行:
- 聚合根作为事务边界
- 聚合内强一致
- 聚合间最终一致
2
3
4
5
6
7
8
举例:订单聚合
交易上下文:
┌────────────────────────────────────┐
│ Order (聚合根) │
│ - orderId │
│ - buyerId ← ID 引用 │
│ - items: List<OrderItem> │ ← 内部实体
│ - totalAmount: Money │ ← 值对象
│ - status: OrderStatus │
│ │
│ methods: │
│ - addItem(...) │
│ - pay(...) │
│ - cancel(...) │
└────────────────────────────────────┘
↑
不直接持有 Buyer 对象
不直接持有 Product 对象
需要详情时通过 ACL/事件
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
为什么聚合内引用要用 ID?
❌ 直接持有对象引用
class Order {
Buyer buyer; // 持有跨上下文对象
List<Product> products;
}
→ 加载 Order 必须加载 Buyer 和 Products
→ 跨上下文调用、级联加载、性能崩溃
✅ 用 ID 引用
class Order {
BuyerId buyerId;
List<ProductId> productIds;
}
→ Order 自给自足
→ 需要 Buyer 详情时显式调用
→ 边界清晰、性能可控
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
详细的聚合设计原则见 对象设计与建模。
# 7.3 领域事件作为接口
上下文之间最优的协作方式 = 领域事件。
交易上下文 消息总线 营销上下文
│ │ │
├─ Order.pay() │ │
├─ 发布 OrderPaidEvent ──────►│ │
│ {orderId, buyerId, │ │
│ amount, paidAt} ├──────────────────────────►│
│ │ 订阅 OrderPaidEvent │
│ │ ├─ 自动计算积分
│ │ ├─ 检查满返活动
│ │ └─ ...
红利:
- 交易上下文不需要知道营销存在(解耦)
- 营销可以独立增加新的业务规则(开放扩展)
- 事件即跨上下文契约(用 PL 语言描述)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
领域事件作为接口的优势:
| 维度 | 同步 RPC | 领域事件 |
|---|---|---|
| 耦合度 | A 必须知道 B 的存在 | A 只需要发事件 |
| 性能 | 同步等待 | 异步处理 |
| 可用性 | B 挂了 A 也卡 | B 挂了不影响 A |
| 扩展性 | 加 C 要改 A | 加 C 只要订阅事件 |
| 一致性 | 强一致 | 最终一致 |
详细实现见 04.事件驱动架构设计。
# 7.4 与六边形CQRS联动
战略设计是起点,配合六边形/CQRS/事件驱动才能完整落地:
[06.DDD战略设计] → 定义上下文边界、语言、映射
│
▼
[02.六边形架构] → 每个上下文内部,领域核心独立于框架
│
▼
[03.CQRS] → 重负载上下文内部,读写分离
│
▼
[04.事件驱动] → 上下文之间,用事件解耦
│
▼
[05.微服务拆分] → 把上下文物理拆成独立服务(如果需要)
2
3
4
5
6
7
8
9
10
11
12
13
完整落地图:
┌───────── 业务方 ─────────┐
│ │
│ 通用语言 (第 3 章) │
│ │
└──────────┬───────────────┘
▼
┌─────────────────────────────┐
│ 上下文映射图 (第 6 章) │
│ 交易 ──事件──→ 营销 │
│ 交易 ──ACL──→ 外部支付 │
└──────┬───────────┬──────────┘
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 交易上下文 │ │ 营销上下文 │
│ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │六边形架构 │ │ │ │六边形架构 │ │
│ │ +CQRS │ │ │ │ (轻 CQRS) │ │
│ │ +事件溯源 │ │ │ │ │ │
│ └─────────────┘ │ │ └─────────────┘ │
└─────────────────┘ └─────────────────┘
独立服务 + 独立数据库 独立服务 + 独立数据库
(微服务拆分,第 5 章)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 8. 战略反模式
# 8.1 全公司统一模型
最常见、最害人的反模式——追求"全公司只有一个 Customer / 一个 Order / 一个 User"。
❌ 反模式:统一大模型
Customer (87个字段)
┌──────────────────┐
│ id, name │ ← 登录上下文用
│ password │ ← 登录上下文用
│ level, points │ ← 会员上下文用
│ tags, rfm │ ← 营销上下文用
│ taxId, invoice │ ← 财务上下文用
│ contact, ticket │ ← 客服上下文用
│ ...87个字段 │
└──────────────────┘
↑↑↑↑↑↑
所有上下文共用
2
3
4
5
6
7
8
9
10
11
12
13
14
后果:
- 字段膨胀——每个团队都要加自己需要的字段,到 100+ 字段后查询性能崩溃
- 变更地狱——会员上下文加字段,登录、营销、财务全部要协调升级
- 权限失控——客服能看到所有字段,包括财务的发票信息
- 语义混乱——"isActive" 在登录上下文是"账号未冻结",在营销上下文是"30天内有交互",混在一个字段里说不清
修复:按上下文拆开,每个上下文一份自己的 Customer 模型:
✅ 上下文内独立模型
登录上下文.Account 营销上下文.Customer
- accountId - customerId
- credential - tags
- status - segments
会员上下文.Member 财务上下文.Payer
- memberId - payerId
- level - taxId
- points - invoiceInfo
每个上下文 5~15 字段
通过 ACL 或事件互相协作
2
3
4
5
6
7
8
9
10
11
12
13
14
# 8.2 上下文即微服务
第二个常见误区——机械地"一个上下文一个微服务"。
❌ 误区
上下文 A → 必须独立微服务 A
上下文 B → 必须独立微服务 B
上下文 C → 必须独立微服务 C
2
3
4
真相:上下文是逻辑边界,微服务是物理边界——不必一一对应。
合理对应方式:
- 模块化单体: 多个上下文 同一个进程,但内部强模块边界
- 单服务多上下文: 1 个微服务装 2~3 个紧密协作的上下文
- 单上下文多服务: 1 个超大上下文按读/写/批拆成多个服务 (CQRS)
- 上下文即微服务: 1:1 对应 (最常见)
2
3
4
5
何时合并多个上下文到一个服务:
- 团队规模 < 30 人 → 全部上下文在一个模块化单体里
- 上下文之间调用极频繁 → 合并为一个服务避免网络开销
- 上下文还在快速演进,边界不稳 → 暂不物理拆分
何时把一个上下文拆成多个服务:
- 读 QPS >> 写 QPS → 拆成读服务 + 写服务(CQRS)
- 实时业务 + 批处理 → 拆成在线服务 + 批处理服务
- 巨大上下文(> 30 万行代码)→ 内部再分子上下文
先有边界,再选物理形态——别本末倒置。详见 05.微服务拆分策略 第 6 章。
# 8.3 贫血战略文档
第三个反模式——画了一堆战略文档,但跟代码完全脱节。
❌ 贫血战略文档症状:
- Wiki 里有一份"领域模型图"——3 年前画的,没人维护
- 代码里类名/字段名与术语表完全对不上
- 新人入职先看文档,看完一脸懵——"代码里根本找不到这些概念"
- 业务方提需求时,产品再翻译一遍——退回"翻译损耗"
2
3
4
5
根因:战略设计被当成"文档工作"——开会画图、写 Wiki、然后归档。没有任何机制把文档同步到代码。
修复:
| 文档类型 | 同步机制 |
|---|---|
| 术语表 | 进入 Code Review 检查清单——命名不符合术语表 → reject |
| 上下文映射图 | 与代码仓库映射,每个上下文 = 一个独立 repo / module |
| 子域分类 | 进入项目立项模板——立项时必须标明属于哪个子域 |
| 领域事件 | 用 Schema Registry 管理,事件定义即接口契约 |
铁律:战略文档要么进入日常工作流,要么就别写——写了不维护,比不写还误导。
# 8.4 模型与组织失配
第四个反模式——画好了上下文模型,但组织结构没跟着调整。
❌ 失配场景:
模型层划分了 5 个上下文
组织层只有 2 个团队
→ 每个团队管 2~3 个上下文
→ 每个上下文有 1~2 个团队的人改
→ 半年后模型必然失控
或反过来:
模型层划分了 3 个上下文
组织层有 8 个团队
→ 每个上下文 2~3 个团队抢着改
→ 互相冲突, 沟通成本爆炸
2
3
4
5
6
7
8
9
10
11
12
康威定律的双向作用:
正向: 组织结构 → 系统结构
(你的团队怎么分,你的系统就长成什么样)
反向: 想要的系统结构 → 必须调整组织结构
(想拆出独立上下文,必须先有独立团队)
2
3
4
5
修复决策:
当模型与组织失配时, 二选一:
A. 调整组织以适配模型 (理想,需要 VP 级支持)
B. 调整模型以适配组织 (现实,接受耦合的代价)
C. (最差) 维持失配,假装没事
→ 半年后必然回到第 1 章的混乱
2
3
4
5
6
实操建议:做战略设计之前先评估组织变更的可能性——如果组织铁打不动,就别画一个 5 上下文方案给 2 团队执行。
# 8.5 反模式五连击集锦
实战中最常踩的坑汇总:
反模式 1 · 全公司统一模型
❌ 一个 Customer 表 87 字段管所有
✅ 每个上下文独立 Customer 模型
2
反模式 2 · 上下文机械等于微服务
❌ 5 个上下文必须 5 个微服务
✅ 按团队规模 + 调用频率决定物理形态
2
反模式 3 · 战略文档不进代码
❌ Wiki 写一遍, 代码命名各写各的
✅ 术语表 = Code Review 红线
2
反模式 4 · 模型与组织失配
❌ 画 5 个上下文给 2 个团队
✅ 模型设计必须考虑组织实际
2
反模式 5 · 跨上下文共享聚合根
❌ Order 持有 Customer 对象引用
✅ Order 只持有 CustomerId, 需要详情走 ACL/事件
2
# 9. 事件风暴实战
# 9.1 三色便利贴法
事件风暴(Event Storming) 是识别上下文最有效的工具——Alberto Brandolini 2013 年提出,3 天工作坊 = 6 个月架构会议的效果。
核心物料:
┌─────────────────────────────────────────────────────────┐
│ 材料清单 │
│ - 一面 4~6 米长的墙 (或长会议室白板) │
│ - 5 种颜色便利贴 (大量): │
│ 🟠 橙色 → 领域事件 (过去时,如"订单已支付") │
│ 🔵 蓝色 → 命令 (动词,如"提交订单") │
│ 🟡 黄色 → 角色/参与者 (人或外部系统) │
│ 🟣 紫色 → 业务策略/规则 │
│ 🔴 红色 → 问题/疑惑/争议点 │
│ - 厚记号笔每人一支 │
│ - 全员到场: 产品/开发/测试/运营/客服/部分业务方 │
└─────────────────────────────────────────────────────────┘
2
3
4
5
6
7
8
9
10
11
12
为什么这五种便利贴?
| 颜色 | 含义 | 作用 |
|---|---|---|
| 🟠 事件 | "发生了什么" | 业务关心的事实,最稳定 |
| 🔵 命令 | "谁做了什么" | 触发事件的动作 |
| 🟡 角色 | "谁发起命令" | 用户/外部系统 |
| 🟣 策略 | "什么时候自动做什么" | 业务规则/自动化 |
| 🔴 问题 | "我不确定/不同意" | 暴露歧义和盲区 |
核心思想:以事件为骨架建模——事件是业务的本质(发生了就发生了),命令/角色/策略都是为事件服务的。
# 9.2 大图工作坊流程
3 天工作坊的标准流程(Big Picture EventStorming):
Day 1: 发散
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
09:00 介绍方法、规则、目标
09:30 第 1 轮:所有人写事件 (橙色)
- 不讨论、不评判,30 分钟猛写
- 每人 30+ 张事件
- 全部贴到墙上 (无序)
10:30 第 2 轮:按时间排序
- 拉出一条时间轴
- 把事件按发生顺序排
- 删除重复、合并相似
12:00 午餐
14:00 第 3 轮:补充罕见事件
- "异常路径"事件 (订单已取消、支付已失败)
- "管理操作"事件 (商品已下架、客服已介入)
16:00 第 4 轮:标注热点
- 红色便利贴贴在争议点
- "这里到底什么时候触发?"
- "这两个事件是同时还是先后?"
17:00 Day 1 收尾 — 墙上应有 200~500 张事件
Day 2: 收敛
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
09:00 第 5 轮: 找出"主流程"
- 用粗线圈出主要业务流程
- 一般是 3~5 条线 (下单流程、退款流程、客服流程...)
10:30 第 6 轮: 标注命令和角色
- 蓝色: 每个事件前面贴上触发它的命令
- 黄色: 每个命令前面贴上发起者
14:00 第 7 轮: 识别聚合
- 命令+事件对作用在哪个对象上?
- 用框圈出聚合 (Order/Product/Member...)
16:00 第 8 轮: 找上下文边界
- 哪些聚合天然属于一组?
- 在它们外面画大框 → 候选上下文
17:00 Day 2 收尾 — 墙上应有清晰的上下文边界
Day 3: 验证
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
09:00 第 9 轮: 走查每条主流程
- 业务方逐一讲解
- 开发挑战可行性
- 红色问题逐一消除
14:00 第 10 轮: 标注上下文映射
- 上下文之间的事件流向
- 同步调用 vs 异步事件
- 选择 9 模式之一
16:00 第 11 轮: 产出物拍照存档
- 全墙拍照
- 转录到数字工具 (Miro/draw.io)
- 产生上下文映射图 + 术语表初稿
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 9.3 设计级事件风暴
大图风暴解决"上下文怎么划"——设计级风暴(Design-Level EventStorming) 进一步解决"上下文内部怎么建模"。
用法:选定一个上下文,单独再开一场 1~2 天小风暴,目的是产出战术建模物料。
聚焦在一个上下文 (如交易上下文)
│
▼
重新展开事件流
│
▼
为每个事件细化:
- 命令携带的数据 (Command Payload)
- 事件携带的数据 (Event Payload)
- 触发命令所需的"前置条件" (绿色便利贴)
- 命令的"输入数据" (粉色便利贴)
│
▼
识别聚合根:
- 谁是事件的发出者
- 谁是命令的接收者
- 一致性边界在哪
│
▼
产出物:
- 聚合根列表 + 字段
- 命令-事件清单 (= 接口契约)
- 业务策略清单 (= 领域服务)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
设计级 vs 大图级:
| 维度 | 大图级 | 设计级 |
|---|---|---|
| 范围 | 全公司业务 | 一个上下文 |
| 时长 | 2~3 天 | 1~2 天 |
| 参与者 | 全员 | 上下文相关团队 |
| 产出 | 上下文地图 | 聚合 + 命令 + 事件 |
| 后续 | 启动战略落地 | 直接开始战术编码 |
# 9.4 产出物与跟进
工作坊产出物清单与跟进机制:
产出物 1 · 事件风暴墙照片
归档到团队共享空间
转录到 Miro/draw.io (永久保存)
作为新人入职第一份资料
2
3
产出物 2 · 上下文映射图
明确标注:
- 上下文名称 + 主要聚合
- 上下文间的关系 (用 9 模式之一标注)
- 关键事件流方向
存放: 项目仓库 architecture/context-map.md
更新: 每季度 review 一次
2
3
4
5
6
7
产出物 3 · 通用语言术语表 v0.1
每个上下文一份
包含: 概念名 + 定义 + 同义词 + 反义词 + 代码命名
存放: 项目仓库 docs/glossary/{context}.md
更新: 持续维护 (第 3.4 节纪律)
2
3
4
产出物 4 · 待解决问题清单
墙上所有红色便利贴汇总
每条指派负责人 + Deadline
分类: 业务待定 / 技术待研究 / 跨团队沟通
跟进: 周会复盘进度
2
3
4
5
产出物 5 · 下一步行动计划
- 立项: 哪些上下文需要新建/重构
- 团队调整: 是否需要拆分/合并团队
- 优先级: 先做哪个上下文
- 度量: 用什么指标判断成功 (如开会沟通成本下降 50%)
2
3
4
红线:风暴开完不跟进 = 白开。3 个月内必须看到代码层面的变化(新仓库、新模型、新接口),否则就是新一份"贫血战略文档"。
# 10. 综合案例串讲
# 10.1 案例真相揭晓
回到第 1 章的 60 人团队"客户"五种含义事故,七个疑问现在能逐条作答:
| 疑问 | 答案 |
|---|---|
| ① 为什么"全公司一个统一模型"行不通? | 第 8.1:87 字段大表导致字段膨胀、变更地狱、权限失控、语义混乱——业务概念本来就有边界 |
| ② 业务概念的边界怎么找? | 第 4.2 / 4.3:同名异义识别法 + 四标尺打分(语言/规则/生命周期/团队) |
| ③ 不同上下文里同名概念怎么共存又不打架? | 第 6:9 种上下文映射模式,最常用的是防腐层(ACL)和领域事件 |
| ④ 哪些子域值得砸钱自研,哪些买就行? | 第 5:核心子域重金自研(差异化竞争力),支撑适度,通用必买 |
| ⑤ 业务方和开发为什么经常"说同一个词不是一回事"? | 第 3.2:翻译损耗——六层翻译信噪比剩 35%;统一语言可拉回到 90%+ |
| ⑥ 战略画完了,到代码层怎么落? | 第 7:每个上下文内部独立做战术建模 + 通过事件/ACL 协作 |
| ⑦ 怎么把全公司业务模型一次理清? | 第 9:事件风暴 3 天工作坊,效果 >> 6 个月架构会议 |
修复方案(按代价从小到大):
方案 A · 拆"客户"为多模型(推荐起步)
按上下文重建 5 个独立模型:
- 认证上下文.Account (5 字段: id, account, credential, status, createdAt)
- 会员上下文.Member (10 字段: memberId, level, points, joinDate, ...)
- 营销上下文.Customer (8 字段: customerId, tags, segments, rfm, ...)
- 交易上下文.Buyer (7 字段: buyerId, addresses, paymentMethods, ...)
- 客服上下文.Complainant (6 字段: complainantId, contacts, tickets, ...)
跨模型协作通过:
- 注册时 Account 发布 AccountRegisteredEvent → 触发 Member 创建
- 下单时 Buyer 通过 BuyerId 引用,需要等级信息时调用会员上下文 API
- 营销时 Customer 通过 ACL 同步会员数据 + 交易数据
代价: 2 个月重构,需要数据迁移
收益: 87 字段 → 5 个上下文各自 5~10 字段;变更地狱消失;权限可控
2
3
4
5
6
7
8
9
10
11
12
13
14
方案 B · 引入统一语言守护机制
立刻动作:
- 5 个上下文各自建立术语表 (v0.1)
- Code Review 加入"命名符合术语表"检查项
- 新需求评审增加"概念明确"环节 (业务方必须给定义)
- 每季度术语表 review
代价: 文化建设需 3~6 个月生效
收益: 跨团队歧义大幅减少,需求评审效率提升 50%+
2
3
4
5
6
7
8
方案 C · 调整组织结构(最激进)
原后端组 20 人拆分为:
- 交易团队 5 人 (负责交易上下文)
- 营销团队 5 人 (负责营销上下文)
- 会员团队 4 人 (负责会员上下文)
- 平台团队 6 人 (负责认证/通知/客服等支撑+通用)
每个团队完全负责自己的上下文:
- 独立代码仓库
- 独立数据库
- 独立 CI/CD
- 对外通过明确契约 (OHS + PL)
代价: 组织调整需 VP 级支持,3~6 个月磨合期
收益: 彻底符合康威定律,跨团队冲突归零
2
3
4
5
6
7
8
9
10
11
12
13
14
生产建议:方案 B 立刻做(成本低收益快),方案 A 排到下半年(系统性重构),方案 C 视组织意愿(最根本但阻力大)。三方案叠加 = 真正解决问题。
# 10.2 一次战略落地全过程
把"从战略到代码"的全过程串成一棵知识树:
决策"重新设计领域模型"
│
├─ 准备阶段
│ ├─ 业务复杂度评估 — 是否值得做战略设计
│ ├─ 高层支持获取 — VP 级 sponsorship
│ ├─ 关键人物到位 — 业务/产品/技术核心 lead
│ └─ 物料准备 — 会议室、便利贴、记号笔
│
├─ 发现阶段 (事件风暴大图,第 9.2 节)
│ ├─ Day 1: 发散写事件 (200~500 张)
│ ├─ Day 2: 收敛识别上下文
│ └─ Day 3: 验证 + 上下文映射
│
├─ 分类阶段 (第 5 章)
│ ├─ 标注核心 / 支撑 / 通用子域
│ ├─ 决定每个子域的投资策略
│ ├─ 决定哪些自研、哪些买
│ └─ 制定团队-上下文映射 (第 4.4 节)
│
├─ 语言阶段 (第 3 章)
│ ├─ 每个上下文起草术语表 v0.1
│ ├─ 业务方 + 开发共同评审
│ ├─ 进入代码评审检查清单
│ └─ 建立日常维护机制
│
├─ 集成阶段 (第 6 章上下文映射)
│ ├─ 标注每对上下文之间的 9 模式
│ ├─ 核心:防腐层 (ACL) 隔离外部
│ ├─ 内部:领域事件解耦
│ └─ 制定 SLA、版本化策略
│
├─ 战术阶段 (第 7 章 + 设计级风暴)
│ ├─ 每个上下文单独开 1~2 天设计级风暴
│ ├─ 识别聚合根 + 命令 + 事件
│ ├─ 设计资源库 + 领域服务
│ └─ 决定与六边形/CQRS/事件驱动的搭配
│
├─ 落地阶段 (与微服务拆分协同)
│ ├─ 决定物理形态:模块化单体 / 微服务
│ ├─ 数据库按上下文拆分 (一库一服务)
│ ├─ 旧系统:绞杀者模式渐进迁移
│ └─ 灰度切流 + 监控验证
│
└─ 反馈阶段
├─ 3 个月后复盘:边界画对了吗?
├─ 业务沟通效率是否提升?
├─ 跨上下文变更频率是否下降?
├─ 必要时调整 (上下文合并/拆分)
└─ 下一次战略 review (回到准备阶段)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
理解一次战略落地的完整路径,就是理解为什么 DDD 是一种长期实践,不是一次性架构决策。每次战略 review 都应该让模型更贴近业务现实。
# 10.3 设计哲学回扣
整理本篇的四条跨架构适用的设计哲学:
哲学 1:边界即语言——同一个词在不同地方是不同的东西
最深刻的认知:没有"全公司统一的客户"——"客户"在登录、会员、营销、交易、财务、客服里本来就是六种不同的东西。强行统一就是把六个清晰的概念糅成一团泥。承认边界的存在,比假装边界不存在更接近真理。这条哲学是 DDD 战略的灵魂——也是为什么"限界上下文"要叫"限界"(Bounded)而不是"上下文"。
哲学 2:核心稀缺——资源永远要砸在差异化上
任何公司的核心子域只占 20~30%,但应得 50%+ 的资源。把核心子域当通用子域 = 自废武功;把通用子域当核心子域 = 资源浪费。清晰区分哪里是护城河、哪里是基建,是 CTO 级别的核心判断力。这条哲学不止用于 DDD——延伸到任何资源分配场景:技术选型、人力配置、预算分配,都遵循同样的二八法则。
哲学 3:语言即模型——业务和开发说同一种话
代码里的类名、字段名、方法名,必须与业务方嘴里的词一致。每多一层翻译就丢一层语义;六层翻译下来,业务原意只剩 1/5。统一语言不是"开发去背业务词"或"业务去学技术词"——是双方一起重新定义一套共用的词。这套词写进术语表、写进代码、写进文档,任何不符合的命名都视为债务。语言纪律是 DDD 落地与否的最终标志。
哲学 4:康威双向——组织决定系统,系统也反作用组织
正向:你团队怎么分,你的系统就长成什么样——三个团队画不出五上下文方案,方案再美也要碎裂。反向:想要的系统结构倒逼组织调整——想拆出独立上下文,必须先有独立团队负责。架构师的最高境界不是画好图,是推动组织调整以匹配图。这往往不是技术问题,是政治问题——能否成事,看你能不能让 VP 级支持。
# 10.4 战略设计速查
一张图保存以备查:
| 要素 | 用途 | 关键决策 | 输出物 |
|---|---|---|---|
| 领域 | 公司业务整体 | 一句话使命 | 使命陈述 |
| 子域 | 业务能力切分 | 核心/支撑/通用三级 | 子域地图 |
| 限界上下文 | 模型+语言+团队边界 | 同名异义识别+四标尺 | 上下文列表 |
| 通用语言 | 业务-开发共识词 | 术语表持续维护 | 术语表 v.x |
| 上下文映射 | 上下文协作关系 | 9 模式选其一 | 映射图 |
九种上下文映射模式速查:
| 模式 | 适用 | 关键词 |
|---|---|---|
| Partnership | 深度协作的核心上下文 | 共担成败 |
| Shared Kernel | 极少数稳定公共模型 | 共享代码(慎用) |
| Customer/Supplier | 内部上下游 | 有发言权 |
| Conformist | 强势外部系统 | 不抗争 |
| ACL(防腐层) | 外部/老旧系统对接 | ★ 翻译适配 |
| Open Host Service | 核心上游多下游 | 标准化接口 |
| Published Language | 跨组织协议 | 共同协议 |
| Separate Ways | 业务无关 | 主动不集成 |
| Big Ball of Mud | 反模式 | 必须重构 |
战略落地决策树:
开始 DDD 战略
│
▼
业务复杂度足够大?
(10+ 概念交织)
/ \
是 否
↓ ↓
开事件风暴大图 不需要 DDD
↓ 直接分层架构
识别上下文 (3~10个)
↓
子域分类:
核心/支撑/通用
↓
团队与上下文对齐?
/ \
是 否
↓ ↓
起草术语表 要么调团队
↓ 要么调上下文 (合并)
标注上下文映射
↓
设计级风暴每个上下文
↓
选物理形态:
模块化单体 / 微服务
↓
与六边形/CQRS/事件驱动联动
↓
持续演进 + 定期 review
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
60 秒诊断命令清单(用于判断现状):
# 看是否有"统一大模型"症状
grep -r "class User\b\|class Customer\b" src/ | wc -l # 用户/客户类被引用次数
mysql> DESC customer; # 看主表字段数
# 看是否有"翻译损耗"症状
grep -E "// 业务说.*实际是" src/ # 注释里出现"业务说"
git log --grep="字段含义" --oneline | wc -l # 因字段含义产生的提交
# 看是否有"贫血战略文档"症状
ls docs/architecture/ # 架构文档最后更新时间
git log --follow docs/glossary.md --since="6 months" # 术语表 6 个月内是否更新
# 看是否有"组织失配"症状
cat CODEOWNERS | sort | uniq -c # 同一模块多 owner = 失配
git log --pretty='%ae' --since='3 months' src/order/ | sort -u | wc -l
# 同一目录贡献者数量
# 看上下文物理边界
find . -name "package.json" -o -name "pom.xml" | head # 仓库数量
docker ps | grep production | awk '{print $NF}' | sort -u | wc -l
# 服务数量
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
战略设计黄金法则:
业务理解: 业务方和开发说同一种话 (统一语言)
边界识别: 同名异义 + 四标尺 + 团队归属
子域投资: 核心重金自研、支撑适度、通用必买
上下文映射: 默认 ACL,跨域用事件,绝不共享模型
组织对齐: 模型与团队 1:1,失配必须调整
战术衔接: 上下文内独立战术建模,聚合内 ID 引用
持续演化: 每季度 review,术语表常更新,文档进代码
反模式警惕: 统一大模型/机械微服务/贫血文档/组织失配
落地节奏: 先模块化单体,再选择性微服务,绝不大爆炸
红线纪律: 代码命名 ≠ 术语表 → reject;新词无定义 → 不评审通过
2
3
4
5
6
7
8
9
10
第 1 章案例:60 人 SaaS 公司"客户"五种含义 → 拆为 5 个上下文独立模型 + 统一语言守护 + 组织调整 → 跨团队沟通效率提升 60%,需求误传问题归零,新人入职上手周期从 2 个月缩到 2 周。这就是"边界 + 语言 + 组织"三位一体给团队的核心红利。
下一篇:我们已经知道了"如何识别和组织业务边界",下一步进入 07.架构评审方法论——把"架构设计"从直觉判断升级到 ATAM/质量属性场景/风险风暴等可重复的方法论框架。