4. 上下文映射:团队间协作模式
在领域驱动设计中,上下文映射(Context Map) 是连接战略设计与战术实现的桥梁。它不关心单个限界上下文内部的实体或值对象,而是聚焦于多个限界上下文之间的协作关系与集成方式。理解并正确选择团队间的协作模式,是避免“分布式单体”和“服务调用地狱”的关键。
本章将深入剖析四种核心协作模式:合作关系(Partnership)、共享内核(Shared Kernel)、客户-供应商(Customer-Supplier) 以及 防腐层(Anti-Corruption Layer),并结合 Go 语言工程实践给出落地建议。
4.1 协作模式全景图
不同的协作模式对应不同的组织关系、技术耦合度与变更频率。下表从关键维度对四种模式进行对比:
| 模式 | 组织关系 | 耦合度 | 变更协调 | 适用场景 |
|---|---|---|---|---|
| 合作关系 | 对等团队 | 低-中 | 频繁同步 | 两个上下文需频繁交换数据,共同演进 |
| 共享内核 | 紧密协作 | 高 | 极高 | 核心领域模型高度重叠,需复用同一套核心 |
| 客户-供应商 | 上下游 | 中 | 按契约变更 | 上游提供能力,下游消费,有明确接口 |
| 防腐层 | 隔离 | 低 | 低 | 集成遗留系统或第三方,保护领域模型 |
internal/core 包,被多个 Bounded Context 引用;而 防腐层 则表现为 adapter 包中的接口适配器,将外部模型转换为内部领域模型。
4.2 合作关系(Partnership)
合作关系 发生在两个对等团队之间,他们需要频繁交换信息以完成一个共同的目标。双方没有明确的上下游,而是通过同步接口或事件驱动的方式保持数据一致。
- 特征: 双向依赖、频繁通信、共同计划迭代。
- 风险: 容易演变为“紧耦合”,一个团队的变更会阻塞另一个团队。
- Go 实践: 使用
interface定义双向契约,双方各自实现。推荐使用 gRPC 双向流 或 消息队列(如 NATS) 实现异步解耦。
// 合作关系下的双向接口定义示例
type OrderService interface {
CreateOrder(ctx context.Context, order *Order) error
OnPaymentReceived(ctx context.Context, payment *Payment) error
}
type PaymentService interface {
ProcessPayment(ctx context.Context, payment *Payment) error
OnOrderConfirmed(ctx context.Context, orderID string) error
}
4.3 共享内核(Shared Kernel)
共享内核 是耦合度最高的模式。多个限界上下文共享同一份领域模型的核心部分,例如 User、Money、Address 等值对象或实体。这部分代码由所有相关团队共同维护,任何修改都需要所有团队评审。
import cycle 和包依赖混乱。建议只共享 值对象 和 领域事件,避免共享包含业务逻辑的实体或聚合根。
共享内核的典型结构:
// shared/kernel/valueobject.go
package kernel
import "github.com/shopspring/decimal"
type Money struct {
Amount decimal.Decimal
Currency string
}
// 不可变,只通过 NewMoney 创建
func NewMoney(amount decimal.Decimal, currency string) Money {
return Money{Amount: amount, Currency: currency}
}
4.4 客户-供应商(Customer-Supplier)
这是最常见的上下游模式。供应商(上游) 提供能力或数据,客户(下游) 消费这些能力。双方通过明确的契约(Contract) 进行交互,供应商负责实现契约,客户依赖契约。
- 上游(Supplier): 定义接口、发布事件、提供 SDK 或 API。
- 下游(Customer): 调用接口、订阅事件、实现回调。
- 变更管理: 上游变更契约时,需通知下游并给出迁移期。
在 Go 中,推荐使用 protobuf 定义契约文件,作为团队间的“合同”:
// supplier.proto (上游定义)
service InventoryService {
rpc CheckStock(StockRequest) returns (StockResponse);
}
message StockRequest {
string sku = 1;
int32 quantity = 2;
}
message StockResponse {
bool available = 1;
int32 remaining = 2;
}
4.5 防腐层(Anti-Corruption Layer)
防腐层(ACL) 是保护领域模型不被外部系统“污染”的关键模式。当集成遗留系统、第三方 API 或不同团队维护的上下文时,ACL 负责将外部模型转换为内部领域模型,隔离外部变化。
ACL 的核心组件:
- 接口适配器: 将外部接口转换为内部接口。
- 模型转换器: 将外部 DTO 转换为内部 Entity/Value Object。
- 门面(Facade): 对外部系统的复杂调用进行封装,提供简洁的领域接口。
// 防腐层示例:将外部 LegacyOrder 转换为内部 Order
type LegacyOrderAdapter struct {
client *legacy.Client
}
func (a *LegacyOrderAdapter) ToDomainOrder(ctx context.Context, legacyID string) (*domain.Order, error) {
legacyOrder, err := a.client.GetOrder(ctx, legacyID)
if err != nil {
return nil, err
}
// 转换逻辑:将外部模型映射为内部领域模型
return &domain.Order{
ID: domain.OrderID(legacyOrder.OrderNumber),
Total: domain.Money{Amount: legacyOrder.Amount, Currency: "CNY"},
Status: a.mapStatus(legacyOrder.Status),
}, nil
}
func (a *LegacyOrderAdapter) mapStatus(legacyStatus int) domain.OrderStatus {
// 状态映射表
switch legacyStatus {
case 1:
return domain.OrderStatusPending
case 2:
return domain.OrderStatusConfirmed
default:
return domain.OrderStatusUnknown
}
}
4.6 协作模式选择决策树
在实际项目中,如何快速选择正确的协作模式?以下决策树可以帮助团队做出判断:
4.7 模式选择与 Go 工程实践
在 Go 项目中,协作模式的选择直接影响包结构、接口定义和依赖管理。以下是一些实战建议:
- 合作关系: 使用
internal/包隔离实现,通过pkg/暴露接口。双方各自维护自己的adapter实现。 - 共享内核: 将共享代码放在独立的
shared/或kernel/包中,只包含值对象、领域事件和基础类型。避免在共享包中包含仓储或服务实现。 - 客户-供应商: 供应商提供
client包(SDK),下游通过依赖注入使用。契约文件(proto)由供应商维护,下游通过go mod引用。 - 防腐层: 在
adapter包中实现 ACL,对外部调用进行封装。ACL 内部使用interface定义领域需要的接口,外部实现通过适配器注入。
4.8 本章小结
上下文映射中的协作模式是 DDD 战略设计的核心决策点。正确选择模式可以降低团队间的耦合,提升系统的可演进性。在 Go 语言中,利用 interface、protobuf 和清晰的包结构,可以优雅地实现这些模式。记住:模式是工具,不是教条——根据团队的组织结构和业务需求灵活选择,才是架构师的智慧。