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)是描述多个限界上下文之间关系的可视化工具。它帮助团队理解系统全局的集成点、翻译成本和耦合程度。映射图不是架构图,而是关系图——重点在于上下文之间的连接类型,而非内部结构。

上下文映射图示例 —— 电商系统 订单上下文 Order Bounded Context 库存上下文 Inventory Bounded Context 支付上下文 Payment Bounded Context 物流上下文 Logistics Bounded Context 通知上下文 Notification Bounded Context 合作关系 防腐层 开放主机服务 事件发布 映射图关键要素 • 每个矩形代表一个限界上下文,使用不同颜色区分业务领域 • 连接线代表上下文之间的关系类型(合作关系、防腐层、开放主机服务等) • 箭头方向代表依赖方向或数据流向
映射图的价值: 上下文映射图是架构决策的沟通工具。当团队讨论“订单系统如何与库存系统集成”时,映射图提供了精确的关系类型术语,避免使用模糊的“调用”“依赖”等词汇。

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 开放主机服务 定义契约,支持多客户端
两个上下文几乎没有业务关联 分离方式 避免不必要的耦合
需要与多个上下文共享少量值对象 共享内核 严格控制共享范围,避免膨胀
关系类型选择决策流程 是否同一团队维护? 是 合作关系 / 共享内核 否 是否可控系统? 是 开放主机服务 否 防腐层(ACL) 提示:决策流程是启发式的,实际项目中还需考虑变更频率、数据一致性要求、团队沟通成本等因素。

4.6 上下文映射的 Go 工程实践

  1. 显式声明边界:在 go.mod 或 internal/ 目录结构中明确每个上下文的包路径。
  2. 接口隔离:上下文之间的依赖只通过接口进行,禁止直接引用内部类型。
  3. 事件驱动:优先使用领域事件进行异步通信,降低时间耦合。
  4. 契约测试:为上下文之间的接口编写消费者驱动契约测试(CDC),确保接口兼容性。
  5. 映射图文档:将上下文映射图作为架构文档的一部分,随代码一起版本管理。
Go 工程检查清单:
✅ 每个限界上下文有独立的 internal/ 目录
✅ 跨上下文引用只通过 interface 或事件
✅ 防腐层包含模型翻译逻辑,而非简单代理
✅ 上下文映射图在项目 Wiki 或 docs/ 目录中维护
✅ 团队对每个上下文之间的关系类型达成共识