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)。下游必须主动适配上游的接口变化,而上游无需关心下游的实现。

上下游关系与防腐层示意 上游(Upstream) 用户服务 下游(Downstream) 订单服务 依赖方向 防腐层(ACL) 适配上游接口,隔离变化
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。

客户方-供应方模式:契约驱动集成 供应方(Supplier) 支付服务 定义 protobuf 契约 共享契约 payment.proto rpc Charge(ChargeReq) returns (ChargeResp) 客户方(Customer) 订单服务 按契约生成客户端代码 供应方发布契约,客户方通过 protobuf 生成 Go 客户端 双方独立部署,仅依赖契约文件
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 发布契约,下游通过防腐层适配,既保证契约一致性,又隔离上游变化。