2. DDD战略设计:限界上下文与上下文映射图

在微服务架构中,最大的挑战不是技术选型,而是如何正确地划分服务边界。DDD(领域驱动设计)的战略设计阶段提供了两件核心武器:限界上下文(Bounded Context)和上下文映射图(Context Map)。它们帮助我们识别领域中的核心资产,并定义服务之间的协作关系。

2.1 限界上下文:微服务的天然边界

限界上下文是DDD中最关键的概念。它定义了一个显式的边界,在这个边界内部,领域模型、术语、业务规则和通用语言(Ubiquitous Language)是统一且自洽的。超出这个边界,同一个术语可能具有完全不同的含义。

核心原则:一个限界上下文对应一个微服务(或一组高度内聚的微服务)。每个上下文内部拥有独立的数据库、独立的部署单元和独立的模型演化节奏。

例如,在电商系统中,“用户”在订单上下文中是一个“买家”(包含地址、信用等级),在物流上下文中是一个“收件人”(包含姓名、电话、位置),在营销上下文中是一个“潜在客户”(包含浏览记录、偏好标签)。如果强行使用同一个“用户”模型,会导致模型臃肿、团队耦合、部署相互依赖。

限界上下文的判定标准

判定维度 说明 示例(订单 vs 库存)
通用语言 团队内部使用的术语是否一致 订单说“下单”,库存说“预占”,含义不同
业务独立性 是否可独立完成一组业务用例 订单需要“创建订单”,库存需要“扣减库存”
数据所有权 谁拥有数据的写权限 订单表由订单服务写入,库存表由库存服务写入
变更频率 业务逻辑的修改频率是否一致 促销规则每周变,物流规则每季度变

2.2 上下文映射图:服务间的协作契约

当多个限界上下文需要交互时,我们需要一张上下文映射图来描述它们之间的关系。这张图不是架构图,而是组织关系和集成策略图。它决定了两个团队如何协作、数据如何流转、模型如何映射。

以下是DDD中常见的6种协作关系模式:

  • 合作关系(Partnership):两个上下文紧密协作,共同完成一个用例。任何一方失败,另一方也无法成功。通常需要同步调用或事务性消息。
  • 共享内核(Shared Kernel):两个上下文共享一部分模型(如值对象或基础实体)。共享部分需要双方团队共同维护,变更成本高。
  • 客户-供应商(Customer-Supplier):上游(供应商)提供数据或能力,下游(客户)消费。上游决定接口,下游适配。典型模式:API提供方与调用方。
  • 遵奉者(Conformist):下游完全遵循上游的模型,不做任何适配。适用于上游模型非常稳定且下游无议价能力的情况。
  • 防腐层(Anti-Corruption Layer, ACL):下游创建一个适配层,将上游的模型翻译成自己内部的模型。这是最推荐的集成模式,能有效隔离外部变化。
  • 开放主机服务(Open Host Service, OHS):上游将内部模型封装成公开的API(如REST/gRPC),下游通过API调用,不直接依赖上游内部模型。
Go实践建议:在Go微服务中,防腐层(ACL)通常实现为一个独立的adapter包。例如,订单服务调用支付服务时,在订单服务内部创建一个payment_adapter.go,将支付服务的proto模型转换为订单服务的领域模型。这样即使支付服务接口变更,也只需修改适配器,不影响订单核心逻辑。
上下文映射图:6种协作关系模式 合作关系 Partnership 同步调用 / 事务消息 共享内核 Shared Kernel 共享模型 / 共同维护 客户-供应商 Customer-Supplier 上游定义接口 / 下游适配 遵奉者 Conformist 完全遵循上游模型 防腐层 Anti-Corruption Layer 适配器翻译模型 / 隔离变化 开放主机服务 Open Host Service 封装为公开API / RPC 选择建议 • 跨团队:优先选ACL+OHS • 同团队:可选Partnership • 第三方依赖:必用ACL • 稳定核心:可用Shared Kernel • 无议价能力:Conformist • 平台型:OHS暴露能力 Go实现ACL示例: // adapter/payment.go type PaymentAdapter struct{ client pb.PaymentClient } func (a *PaymentAdapter) Charge(ctx, order) error

2.3 核心域、支撑域与通用域

在识别出限界上下文后,我们需要对它们进行战略优先级划分。这不是技术分类,而是业务投资决策。DDD将领域划分为三个层次:

  • 核心域(Core Domain):企业最核心的竞争力所在,是差异化优势的来源。需要投入最多的资源、最优秀的团队、最精细的设计。例如,对于外卖平台,调度算法是核心域;对于支付公司,风控引擎是核心域。
  • 支撑域(Supporting Subdomain):业务必需,但不具备核心竞争力。通常需要定制开发,但不需要顶级资源。例如,订单管理、用户管理。
  • 通用域(Generic Subdomain):行业通用能力,有成熟的商业产品或开源方案。直接采购或使用开源组件,避免自研。例如,邮件发送、身份认证、日志监控。
常见误区:很多团队把大量精力花在通用域(如搭建完美的权限系统)上,而核心域却草草了事。记住:核心域决定企业的生死,通用域决定成本。在Go微服务中,通用域应优先选择成熟的第三方库(如casbin做权限,logrus做日志),而不是自研。

领域划分对比表

维度 核心域 支撑域 通用域
业务价值 高(差异化) 中(必需但无差异) 低(行业标准)
开发策略 自研,投入顶级资源 自研,适度投入 采购或开源
变更频率 高(持续迭代优化) 中 低(稳定)
团队要求 领域专家+高级工程师 普通工程师 运维或集成工程师
Go示例 推荐算法、定价引擎 订单服务、库存服务 认证(Keycloak)、日志(ELK)
领域战略优先级划分:核心域 / 支撑域 / 通用域 核心域 Core Domain 差异化竞争力 投入顶级资源 持续创新迭代 例:推荐引擎、风控模型 支撑域 Supporting 业务必需,无差异 适度投入自研 例:订单、库存、用户 通用域 Generic 行业标准能力 采购或开源 例:认证、日志、邮件 战略投资决策矩阵 • 核心域:自研 + 领域驱动设计 + 持续重构 + 顶级团队 • 支撑域:自研 + 简单设计 + 适度测试 + 普通团队 • 通用域:采购/开源 + 集成适配 + 最小维护 + 运维团队

2.4 实战案例:电商平台的领域划分

让我们通过一个电商平台的例子,将上述概念串联起来。假设我们要设计一个电商系统的微服务架构:

  1. 识别限界上下文:通过事件风暴(Event Storming)工作坊,我们识别出以下上下文:商品上下文、订单上下文、支付上下文、库存上下文、物流上下文、营销上下文、用户上下文、通知上下文。
  2. 绘制上下文映射图:确定每个上下文之间的协作关系。例如,订单上下文作为客户调用支付上下文(供应商),并通过防腐层隔离支付模型的变更。
  3. 划分战略优先级:
    • 核心域:营销上下文(个性化推荐、促销引擎)——这是电商的差异化竞争力。
    • 支撑域:订单上下文、库存上下文、物流上下文——业务必需,但行业做法成熟。
    • 通用域:用户上下文(认证)、通知上下文(短信/邮件)——直接使用第三方服务。
Go代码示例:防腐层实现
以下代码展示了订单服务如何通过防腐层调用支付服务,将外部proto模型转换为内部领域模型。
// internal/domain/payment.go (领域层)
package domain

type PaymentResult struct {
    TransactionID string
    Status        PaymentStatus
    Amount        float64
}

type PaymentStatus int
const (
    PaymentSuccess PaymentStatus = iota
    PaymentFailed
)

// internal/adapter/payment.go (防腐层)
package adapter

import (
    "context"
    "github.com/yourproject/internal/domain"
    pb "github.com/yourproject/proto/payment/v1"
)

type PaymentAdapter struct {
    client pb.PaymentServiceClient
}

func NewPaymentAdapter(client pb.PaymentServiceClient) *PaymentAdapter {
    return &PaymentAdapter{client: client}
}

// Charge 将领域模型转换为proto请求,再将proto响应转换为领域模型
func (a *PaymentAdapter) Charge(ctx context.Context, orderID string, amount float64) (*domain.PaymentResult, error) {
    // 1. 领域模型 → proto请求
    req := &pb.ChargeRequest{
        OrderId: orderID,
        Amount:  amount,
    }

    // 2. 调用外部服务
    resp, err := a.client.Charge(ctx, req)
    if err != nil {
        return nil, err
    }

    // 3. proto响应 → 领域模型
    status := domain.PaymentFailed
    if resp.Success {
        status = domain.PaymentSuccess
    }

    return &domain.PaymentResult{
        TransactionID: resp.TransactionId,
        Status:        status,
        Amount:        resp.ChargedAmount,
    }, nil
}
关键设计原则:防腐层位于适配器层(adapter),领域层(domain)永远不依赖外部proto或第三方库。领域层只定义接口(如PaymentService接口),适配器实现该接口。这样,即使支付服务从gRPC切换到REST,也只需修改适配器,领域层纹丝不动。

2.5 本章小结

  • 限界上下文是微服务划分的原子单位,每个上下文拥有独立的通用语言、数据库和部署单元。
  • 上下文映射图定义了服务间的协作模式,防腐层(ACL)是隔离外部变化的首选模式。
  • 核心域/支撑域/通用域决定了资源投入策略:核心域自研创新,通用域采购复用。
  • 在Go中,通过adapter包实现防腐层,保持领域层的纯净和可测试性。