4. 限界上下文:定义、映射图与关系类型
限界上下文(Bounded Context)是 DDD 战术设计的核心单元,也是 Go 工程中划分微服务、模块、包边界的直接依据。一个限界上下文对应一个显式的业务边界,内部拥有统一的领域模型、通用语言和业务规则;边界之外,模型可以有不同的含义甚至不同的名称。
核心原则: 在同一个限界上下文内,一个术语只有一种含义;不同上下文之间,同一个术语可以有不同的含义,通过上下文映射图显式管理这种差异。
4.1 限界上下文的定义
限界上下文由 Eric Evans 在《领域驱动设计》中提出,其本质是语义边界。一个限界上下文包含:
- 领域模型:一组聚合、实体、值对象、领域服务、领域事件。
- 通用语言:团队内部使用的统一术语和沟通语言。
- 业务规则:只在该上下文内成立的不变量和约束。
- 技术实现:数据库、消息队列、API 网关等基础设施的边界。
| 维度 | 说明 | Go 工程体现 |
|---|---|---|
| 模型边界 | 实体、值对象、聚合只在本上下文内定义 | internal/order/ 包内的 Order 结构体 |
| 语言边界 | 术语含义在本上下文内唯一 | “客户”在销售上下文指买家,在售后上下文指投诉人 |
| 数据边界 | 数据库 schema 或存储层独立 | 每个上下文拥有独立的 repository 接口和实现 |
| 部署边界 | 可独立编译、测试、部署 | 独立的 go.mod 或独立微服务 |
Go 实践建议: 在 monorepo 中,每个限界上下文对应
internal/<context-name>/ 目录;在微服务架构中,每个限界上下文对应一个独立的 Go module。禁止跨上下文直接引用内部类型,只通过接口或事件进行交互。
4.2 上下文映射图
上下文映射图(Context Map)是描述多个限界上下文之间关系的可视化工具。它帮助团队理解系统全局的集成点、翻译成本和耦合程度。映射图不是架构图,而是关系图——重点在于上下文之间的连接类型,而非内部结构。
映射图的价值: 上下文映射图是架构决策的沟通工具。当团队讨论“订单系统如何与库存系统集成”时,映射图提供了精确的关系类型术语,避免使用模糊的“调用”“依赖”等词汇。
4.3 上下文之间的关系类型
DDD 定义了 9 种标准关系类型,按耦合度从高到低排列。Go 工程中常用的关系类型如下:
| 关系类型 | 耦合度 | 描述 | Go 实现方式 |
|---|---|---|---|
| 合作关系 (Partnership) | 高 | 两个上下文必须同步协调,否则双方都会失败 | 共享事件总线、分布式事务(Saga) |
| 共享内核 (Shared Kernel) | 高 | 两个上下文共享部分领域模型(通常是小部分核心类型) | 共享 internal/shared/ 包,严格控制变更 |
| 客户-供应商 (Customer-Supplier) | 中 | 上游(供应商)决定接口,下游(客户)适配 | 上游定义 interface,下游实现适配器 |
| 防腐层 (Anticorruption Layer) | 中 | 下游上下文通过一层转换逻辑隔离上游模型的污染 | 适配器模式 + 翻译器(Translator) |
| 开放主机服务 (Open Host Service) | 低 | 上下文对外暴露一组稳定的 API 或协议 | gRPC 服务、RESTful API、Protobuf 定义 |
| 发布语言 (Published Language) | 低 | 上下文使用标准化的数据格式或协议进行通信 | JSON Schema、Protobuf、Avro |
| 分离方式 (Separate Ways) | 无 | 两个上下文完全独立,不进行任何集成 | 无依赖,独立部署 |
| 大泥球 (Big Ball of Mud) | — | 无明确边界,模型混乱交织(反模式) | 需要重构,拆分为独立上下文 |
4.4 防腐层(ACL)的 Go 实现
防腐层是 Go 工程中最常用的关系类型之一。当订单上下文需要调用遗留的库存系统时,ACL 保护订单模型不被库存系统的数据结构污染。
// 订单上下文内部定义的接口(纯净领域模型)
type InventoryService interface {
Reserve(ctx context.Context, sku string, qty int) error
}
// 防腐层适配器:将遗留库存系统的 HTTP API 翻译为领域接口
type LegacyInventoryAdapter struct {
client *http.Client
baseURL string
}
func (a *LegacyInventoryAdapter) Reserve(ctx context.Context, sku string, qty int) error {
// 1. 将领域参数翻译为遗留系统的请求格式
reqBody := legacyReserveRequest{
ProductCode: sku,
Quantity: qty,
WarehouseID: "default",
}
// 2. 调用遗留系统
// 3. 将遗留系统的响应翻译为领域错误
// 4. 返回领域层可理解的 error
return nil
}
常见陷阱: 防腐层不应只是简单的 HTTP 调用封装。真正的 ACL 必须包含模型翻译逻辑——将上游的“ProductCode”翻译为下游的“SKU”,将上游的“status=2”翻译为下游的“ReservationConfirmed”。缺少翻译的 ACL 只是代理,不是防腐层。
4.5 上下文关系选择矩阵
在实际 Go 项目中,选择哪种关系类型取决于以下因素:
| 场景 | 推荐关系 | 理由 |
|---|---|---|
| 两个上下文由同一团队维护,频繁同步变更 | 合作关系 | 降低协调成本,允许直接共享事件 |
| 需要调用外部第三方系统(不可控) | 防腐层 | 隔离外部变化,保护领域模型纯净 |
| 上下文对外提供标准 API | 开放主机服务 | 定义契约,支持多客户端 |
| 两个上下文几乎没有业务关联 | 分离方式 | 避免不必要的耦合 |
| 需要与多个上下文共享少量值对象 | 共享内核 | 严格控制共享范围,避免膨胀 |
4.6 上下文映射的 Go 工程实践
- 显式声明边界:在
go.mod或internal/目录结构中明确每个上下文的包路径。 - 接口隔离:上下文之间的依赖只通过接口进行,禁止直接引用内部类型。
- 事件驱动:优先使用领域事件进行异步通信,降低时间耦合。
- 契约测试:为上下文之间的接口编写消费者驱动契约测试(CDC),确保接口兼容性。
- 映射图文档:将上下文映射图作为架构文档的一部分,随代码一起版本管理。
Go 工程检查清单:
✅ 每个限界上下文有独立的
✅ 跨上下文引用只通过
✅ 防腐层包含模型翻译逻辑,而非简单代理
✅ 上下文映射图在项目 Wiki 或
✅ 团队对每个上下文之间的关系类型达成共识
✅ 每个限界上下文有独立的
internal/ 目录✅ 跨上下文引用只通过
interface 或事件✅ 防腐层包含模型翻译逻辑,而非简单代理
✅ 上下文映射图在项目 Wiki 或
docs/ 目录中维护✅ 团队对每个上下文之间的关系类型达成共识