微服务边界画在哪里,是架构师最头疼的问题之一。按"表"拆、按"模块"拆都试过,最后还是会互相纠缠。DDD 限界上下文给出了一条回答:以领域模型的完整性为界——每个上下文有自己的术语、模型与规则,上下文之间通过明确的关系协作。本文讲清限界上下文的识别、上下文映射,以及共享内核、防腐层、开放主机服务三种关键模式。
1. 什么是限界上下文
1.1 一个词,两个含义
同一个业务词汇在不同场景下含义不同。以"客户"为例:
| 上下文 | “客户"的含义 |
|---|---|
| 下单上下文 | 有地址、能下单的买家 |
| 客服上下文 | 有工单、有聊天记录的求助者 |
| 征信上下文 | 有信用评分、有违约记录的实体 |
如果不加边界,把这些"客户"硬塞进一个模型,就会产生术语冲突与模型污染——A 上下文的规则被 B 上下文误用。
1.2 限界上下文的三要素
- 模型:一组只在本上下文内有意义的领域对象;
- 边界:模型不与外界共享,内部完整自洽;
- 语言:上下文的"无处不在语言”(Ubiquitous Language),术语只在边界内有效。
一句话:限界上下文 = “一个模型 + 一道边界 + 一套术语”——它把大而全的统一模型,切成一簇小而自治的局部模型。
1.3 与微服务边界的关系
限界上下文是逻辑边界,微服务是物理边界。好的实践是一个上下文一个服务(或一个上下文一组服务),但也可以一个上下文先在模块化单体内实现,再渐进拆分(见模块化单体篇)。上下文错了,服务拆得再细也是乱拆。
2. 识别限界上下文的方法
2.1 从业务能力出发
先找高内聚的业务能力,每个能力通常对应一个上下文:
业务能力地图:
订单管理 → 下单上下文
库存管理 → 库存上下文
支付结算 → 支付上下文
客户服务 → 客服上下文
商品运营 → 商品上下文
2.2 从语言变化出发
语言是识别边界最灵敏的信号。当同一个词在两个团队口中含义不同,这里就有一条上下文边界。开会时注意记录:“这个词你们是什么意思?”
2.3 从变化原因出发
“同一时间、同一原因发生的变化应放在同一上下文”——问自己:改客户地址字段,会不会连带触发客服工单结构的变化?如果会,说明边界画错了。
2.4 识别清单
| 信号 | 边界线索 |
|---|---|
| 术语冲突 | 同一名词多团队含义不同 |
| 变化耦合 | 一处修改连锁触发多处 |
| 团队边界 | 与组织结构强相关 |
| 数据归属 | 同一数据被多处按不同规则改 |
2.5 事件风暴辅助识别
事件风暴(Event Storming)是快速识别上下文的实操方法:召集业务与开发,把领域事件(橙贴)、命令(蓝贴)、聚合(黄贴)铺满一面墙,事件密集扎堆的区域往往就是一个上下文。
事件风暴示意:
[订单已提交] [库存已扣减] → 订单上下文区
[库存已扣减] [补货单已生成] → 库存上下文区
两张"库存已扣减"贴纸分属两区 → 边界就在中间
一句话:识别限界上下文 = 顺着语言冲突、变化耦合与团队边界划界——术语变了,模型就该分家;事件风暴是把这种直觉变成集体演练的加速器。
3. 上下文映射 Context Map
3.1 什么是上下文映射
上下文映射把上下文之间的关系画成一张地图,让架构从"一堆服务"变成"一组有明确关系的域"。每种关系都有名字、方向与力度。
3.2 常见关系类型
| 关系 | 方向 | 说明 |
|---|---|---|
| 防腐层(ACL) | 单向 | 下游隔离上游模型的差异 |
| 开放主机服务(OHS) | 单向 | 上游发布公共接口,下游订阅 |
| 发布语言(PL) | 单向 | 与 OHS 配套的公共数据格式 |
| 共享内核(SK) | 双向 | 双方共享同一小模型 |
| 客户/供应商(C/S) | 单向 | 下游是上游的客户 |
| 一致共生(CF) | 双向 | 两个模型必须同步演进(要警惕) |
3.3 示例地图
共享内核
┌─────────────────┐
│ 订单上下文 │
│ (OHS/PL 发布) │
└───┬───────────┬─┘
│ │
[ACL] │ │ [ACL]
▼ ▼
┌──────────┐ ┌──────────┐
│ 库存上下文 │ │ 支付上下文 │
└──────────┘ └──────────┘
一句话:上下文映射是限界上下文之间的关系契约——它把"两个服务怎么协作"显式化,比口头对接更可靠。
4. 共享内核 Shared Kernel
4.1 原理
两个上下文共同维护同一小块核心模型(如用户、合同号),用版本化共享包发布,双方都遵守一致规则。
共享内核(common 包)
├── User.java
├── OrderNo.java
└── Money.java
下游上下文 A ──依赖──┐
下游上下文 B ──依赖──┘
4.2 适用与风险
| 优点 | 风险 |
|---|---|
| 避免重复实现核心规则 | 共享模型变动牵一发动全身 |
| 小范围共享代价低 | 边界被共享打通,自治受损 |
4.3 使用原则
- 共享范围尽量小,只放稳定、通用、规则不变的对象;
- 共享内核要有版本管理与明确的变更流程(谁改、谁评审);
- 一旦共享范围失控,就该拆成"发布语言 + 防腐层"。
一句话:共享内核适合少量高价值且稳定的共享模型——像夫妻共用一个小账户,前提是双方都守规矩、改动走评审。
5. 防腐层 Anti-Corruption Layer
5.1 为什么要防腐
下游上下文不希望被上游的模型、术语、数据结构污染——下游要自己的模型,而不是被迫使用上游的 DTO 与领域规则。
5.2 防腐层的结构
在下游入口建立一层翻译层,把上游的模型翻译成下游自己的模型:
上游服务(订单上下文)
│ REST/gRPC
▼
ACL 防腐层(在下游侧)
- 上游 DTO → 下游领域对象
- 适配协议差异、重命名术语
- 隔离上游变更
▼
下游服务(库存上下文,只认识自己的模型)
5.3 代码示例
// 下游库存上下文:防腐层把上游订单翻译成本地模型
public class OrderTranslator {
public LocalOrder toLocal(RemoteOrder remote) {
return new LocalOrder(
remote.getOrderNo(), // 字段映射
remote.getTotalAmount().toLocal(), // 金额类型适配
LocalStatus.of(remote.getStatus()) // 术语翻译
);
}
}
5.4 防腐层边界职责
- 协议适配:上游 v1/v2、不同接口风格;
- 术语翻译:把上游语言翻译为下游语言;
- 变更缓冲:上游改了,只改 ACL,不改下游核心。
一句话:防腐层是下游自保的护城河——上游怎么变都行,只要翻译层更新,下游的领域模型保持纯净。
6. 开放主机服务 Open Host Service
6.1 原理
上游上下文把一部分能力以公开、稳定、通用的接口(OHS)暴露给下游,并配套**发布语言(Published Language)**作为双方共用的数据格式。
上游(商品上下文)
OHS: /api/products (稳定契约)
PL: ProductDto (公共数据格式)
│
▼
下游1 下游2 下游3(都订阅同一契约,互不感知)
6.2 与 RPC 服务的区别
| 维度 | 内部 RPC | OHS |
|---|---|---|
| 受众 | 单一下游 | 多个下游/外部 |
| 契约 | 随调用方定制 | 公共稳定 |
| 语义 | 操作导向 | 领域能力导向 |
| 版本 | 常变 | 演进受控 |
6.3 实践要点
- OHS 用公共语义定义,不用任何一方的私有模型;
- PL 是独立 schema,不与内部模型绑定;
- OHS 变更走版本化发布,下游按语义化版本升级。
一句话:开放主机服务是上游"面向订阅者"发布公共能力——契约稳定、语义公共,多个下游各自消费,互不污染。
7. 上下文协作模式的选择
7.1 决策表
| 场景 | 推荐模式 |
|---|---|
| 共享少量稳定核心模型 | 共享内核 |
| 下游不想被上游污染 | 防腐层 |
| 上游面向多下游发布能力 | OHS + 发布语言 |
| 上下游强耦合且必须同步 | 客户/供应商(慎用一致共生) |
| 模型不兼容又必须协作 | 防腐层 + OHS 组合 |
7.2 团队与上下文对齐
一个限界上下文最好由一个团队长期负责。上下文是代码边界,也是组织边界——Conway 定律决定了组织结构终将映射到架构结构。上下文与团队错位,是技术债的温床。
7.3 演进路径
上下文映射不是一次画完就不动:随业务变化,共享内核会收紧成 OHS,一致共生会被拆成防腐层。地图要持续维护,如同架构决策记录一样留档。
7.4 ACL 与 OHS 的组合示例
真实系统常组合使用:上游用 OHS 发布,下游用 ACL 承接翻译,两边模型各保持纯净。
上游订单上下文 下游库存上下文
领域模型 ──→ OHS /orders ──→ ACL 翻译层 ──→ 本地模型
(PL 契约) (DTO→Local) (StockReservation)
// 下游 ACL 调用上游 OHS
public void reserveFromOrder(String orderId) {
OrderDto dto = orderClient.getOrder(orderId); // 调 OHS 契约
LocalOrder local = orderTranslator.toLocal(dto); // ACL 翻译
stockDomain.reserve(local.getLineItems()); // 纯领域逻辑
}
一句话:模式选择看关系——稳定共享用共享内核,防污染用防腐层,广播能力用开放主机服务;并让一个上下文归属一个团队。上游 OHS 发布、下游 ACL 承接的组合,是跨上下文协作的默认姿势。
8. 踩坑清单
| 坑 | 现象 | 对策 |
|---|---|---|
| 全公司统一模型 | 一个"用户"到处串 | 按上下文拆分模型 |
| 共享内核失控 | 改动波及所有下游 | 收紧共享范围 + 评审 |
| 防腐层过薄 | 上游字段漏进下游 | 强制翻译层 + 模型映射 |
| OHS 与内部模型耦合 | 改内部导致接口变 | PL 独立 schema |
| 一致共生泛滥 | 两服务必须一起改 | 拆成 ACL 或合并 |
| 上下文地图过期 | 关系失真、无人维护 | 定期评审 + 留档 |
| 团队与上下文错位 | 一个上下文多人乱改 | 对齐组织边界 |
| 术语无人统一 | 会议里鸡同鸭讲 | 维护术语表 |
9. 总结
| 维度 | 结论 |
|---|---|
| 边界依据 | 语言、变化、团队、数据 |
| 关系表达 | 上下文映射 Context Map |
| 防污染 | 防腐层 ACL |
| 广播能力 | 开放主机服务 + 发布语言 |
| 稳定共享 | 共享内核(小范围) |
| 组织对齐 | 一上下文一团队 |
一句话记住:限界上下文划出"模型的国界",上下文映射画出"国与国的外交关系"——共享内核是同盟、防腐层是海关、开放主机服务是外交使馆,边界清晰,微服务才不会内战。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。