4. 上下文映射图:Context Map 概念、上下游关系、共享内核、客户方-供应方模式
在 DDD 战略设计中,上下文映射图(Context Map) 是连接多个限界上下文(Bounded Context)的桥梁。它用一张可视化的关系图,明确描述团队、系统、模块之间的协作方式与集成契约。对于 Go 业务模块而言,Context Map 决定了模块间的通信协议、数据所有权和变更影响范围。
4.1 核心概念与作用
Context Map 不是架构图,而是组织与系统之间的契约图。它回答三个关键问题:
- 谁依赖谁? —— 上下游方向
- 共享什么? —— 数据、事件、API
- 如何集成? —— 同步/异步、防腐层、开放主机服务
| 概念 | 说明 | Go 模块示例 |
|---|---|---|
| 上游(Upstream) | 提供数据或能力的模块,不受下游影响 | user-service 提供用户信息 |
| 下游(Downstream) | 消费上游数据的模块,需适配上游变化 | order-service 调用用户服务 |
| 共享内核(Shared Kernel) | 两个上下文共用同一份核心模型代码 | internal/domain/shared 包 |
| 客户方-供应方(Customer-Supplier) | 上游供应方提供接口,下游客户方按契约消费 | gRPC 服务定义 + protobuf 契约 |
实践要点: 在 Go 项目中,Context Map 通常以
docs/context-map.md 或架构决策记录(ADR)的形式维护,并配合 internal 目录结构体现边界。
4.2 上下游关系与依赖方向
上下游关系决定了谁需要做防腐层(Anti-Corruption Layer)。下游必须主动适配上游的接口变化,而上游无需关心下游的实现。
Go 代码示例:防腐层适配
// 下游订单服务中的防腐层适配器
package adapter
type UserServiceClient interface {
GetUser(ctx context.Context, userID string) (*UserDTO, error)
}
// 防腐层:将上游的 UserDTO 转换为订单上下文中的 UserVO
type UserAdapter struct {
client UserServiceClient
}
func (a *UserAdapter) GetUser(ctx context.Context, userID string) (*UserVO, error) {
dto, err := a.client.GetUser(ctx, userID)
if err != nil {
return nil, fmt.Errorf("user adapter: %w", err)
}
return &UserVO{
ID: dto.ID,
Name: dto.Name,
}, nil
}
4.3 共享内核(Shared Kernel)
共享内核是指两个或多个限界上下文共用同一份领域模型代码。在 Go 中,通常表现为 internal/domain/shared 包。共享内核适合高度耦合、变更频率低的核心概念,如:货币、金额、地址、时间区间。
| 维度 | 共享内核 | 非共享(各自维护) |
|---|---|---|
| 变更成本 | 高(需协调所有使用方) | 低(独立演进) |
| 一致性 | 强(同一份代码) | 弱(可能产生数据不一致) |
| 适用场景 | 基础值对象、通用枚举 | 业务规则、聚合根 |
| Go 包结构 | internal/domain/shared |
internal/domain/order 等 |
避坑提示: 共享内核不要包含业务逻辑,只放值对象和基础类型。一旦共享内核频繁变更,说明边界划分有误,应考虑拆分为独立上下文。
4.4 客户方-供应方模式(Customer-Supplier)
这是最常用的上下游集成模式。供应方(上游)定义接口契约,客户方(下游)按契约消费。在 Go 微服务中,典型实现是 gRPC + protobuf 或 HTTP + OpenAPI。
Go 代码示例:客户方-供应方契约定义
// payment.proto(供应方定义)
syntax = "proto3";
package payment;
service PaymentService {
rpc Charge(ChargeRequest) returns (ChargeResponse);
}
message ChargeRequest {
string order_id = 1;
double amount = 2;
string currency = 3;
}
message ChargeResponse {
string transaction_id = 1;
bool success = 2;
}
// 客户方(订单服务)调用代码
package infrastructure
import (
pb "github.com/company/payment-proto/gen/go/payment"
)
type PaymentClient struct {
client pb.PaymentServiceClient
}
func (c *PaymentClient) Charge(ctx context.Context, req *pb.ChargeRequest) (*pb.ChargeResponse, error) {
return c.client.Charge(ctx, req)
}
4.5 模式选择决策表
在实际 Go 项目中,根据团队协作方式和系统耦合度选择上下文映射模式:
| 模式 | 耦合度 | 变更协调 | 推荐场景 |
|---|---|---|---|
| 共享内核 | 高 | 需双方评审 | 基础值对象、通用枚举 |
| 客户方-供应方 | 中 | 供应方主导 | 微服务间 RPC 调用 |
| 防腐层 | 低 | 下游独立适配 | 集成遗留系统或第三方 |
| 开放主机服务 | 中 | 供应方发布 API | 对外提供 REST/gRPC 接口 |
Go 项目建议: 优先使用 客户方-供应方 + 防腐层 组合。供应方通过 protobuf 发布契约,下游通过防腐层适配,既保证契约一致性,又隔离上游变化。