返回架构笔记
Arch Day 9

Arch Day 9: DDD战略设计(高级)

DDD战略设计的核心不是画"聚合根/实体/值对象"(那是战术设计),而是识别系统中的语言边界(Bounded Context),并明确各个上下文之间的协作关系(Context Mapping),从而在组织层面建立清晰的系统拓扑。

2026-04-08
第一阶段 - 架构基础
DDDContext-Mapping限界上下文战略设计组织拓扑

日期: 2026-04-08 (Day 9) 阶段: 第一阶段 - 架构基础 标签: #DDD #Context-Mapping #限界上下文 #战略设计 #组织拓扑


核心概念

一句话定义

DDD战略设计的核心不是画"聚合根/实体/值对象"(那是战术设计),而是识别系统中的语言边界(Bounded Context),并明确各个上下文之间的协作关系(Context Mapping),从而在组织层面建立清晰的系统拓扑

为什么资深架构师仍需关注

做了10年金融软件,我发现90%的架构问题不是技术问题,而是边界划分问题。DDD战略设计解决的就是这个问题:

  1. 边界决定一切:微服务拆分的边界、团队的职责范围、API的契约——都由Bounded Context决定
  2. Context Map是架构图的前置条件:没有Context Map就画架构图,等于没有地图就开车
  3. 组织政治的可视化工具:Context Mapping的8种关系模式直接映射了团队间的权力关系
  4. 演进式设计的指南针:系统演进时,Context Map告诉你哪些边界是稳定的,哪些需要调整

常见误区与反模式

误区真相后果
"一个微服务=一个Bounded Context"BC是逻辑边界,微服务是部署单元,两者可以1:1也可以1:N要么服务太细(运维灾难)要么太粗(耦合)
"边界越多越好"过多的边界增加集成复杂度团队疲于处理跨上下文通信
"先画战术模型再划边界"战略设计(边界)必须先于战术设计(模型)在错误的边界内做精细建模是浪费
"Bounded Context = 数据库表的分组"BC是语言边界和业务能力边界,不是数据分组边界划分变成了DBA的工作
"Context Map画完就不变了"Context Map应该随组织和业务演进架构与现实脱节
"所有上下文关系都应该是Partnership"Partnership需要极高的协调成本团队被会议淹没

知识点详解

知识点1: Bounded Context的本质——语言边界

Eric Evans定义Bounded Context时,核心强调的是Ubiquitous Language的边界。同一个词在不同上下文中有不同含义:

经典例子:金融系统中的"Account"

上下文"Account"的含义属性
身份认证(Identity)登录账户username, password, MFA
银行账户(Banking)银行账号accountNumber, balance, currency
会计(Accounting)会计科目ledgerCode, debit, credit
客户关系(CRM)客户档案contactInfo, segmentation, riskLevel
交易(Trading)交易账户positions, margin, PnL

如果你在一个系统中用一个Account类同时表达这5种含义,代码会变成一个包含50个字段的"上帝类"(God Class),每个团队修改时都可能影响其他团队。

识别Bounded Context的三个启发式方法:

方法1: 语言边界测试

步骤:
1. 列出系统中所有核心名词(Account/Order/Product/Payment等)
2. 对每个名词,问:"在A团队和B团队的会议上,这个词是同一个意思吗?"
3. 如果不是,这两个团队可能属于不同的Bounded Context

方法2: 团队边界测试

步骤:
1. 画出组织结构图
2. 每个团队的日常工作范围通常暗示了一个Bounded Context
3. Conway定律: 系统结构往往映射组织结构
注意: 不是说组织结构决定BC,而是BC应该与组织结构对齐或者推动组织结构调整

方法3: 业务能力边界测试

步骤:
1. 列出核心业务能力(Business Capabilities)
2. 能独立完成一个完整业务功能的最小集合 = 一个BC
3. 如果拆掉这个BC,哪些业务流程会断?影响范围就是BC的边界

知识点2: Context Mapping 8种关系模式深度解析

Context Mapping定义了两个Bounded Context之间的协作关系。这8种模式不仅是技术关系,更是组织关系和权力关系的映射。

模式1: Partnership (合作关系)

定义: 两个团队成败与共,共同规划接口演进,双方地位平等。

适用场景:

  • 两个团队在同一个项目组,有共同的交付目标
  • 接口变更需要双方同步修改
  • 例:支付团队和订单团队在同一个产品线

组织政治影响:

  • 需要频繁的跨团队会议(至少周会)
  • 任何一方的延期都会影响另一方
  • 适合相邻的、目标高度一致的团队

代码实现:

// 双方共同维护的接口定义(共享仓库或API spec)
// order-service 和 payment-service 共同维护
interface PaymentRequest {
  orderId: string;
  amount: Money;
  currency: Currency;
  idempotencyKey: string;
}

// 变更流程: 双方代码审查 → 同时发布

代价: 协调成本高,不适合超过2-3个团队的关系。

模式2: Shared Kernel (共享内核)

定义: 两个上下文共享一小部分模型代码(通常是值对象或基础类型),双方共同维护。

适用场景:

  • 有一些核心领域概念两边都需要且含义完全一致
  • 例:Money值对象、Currency枚举在多个上下文中通用

组织政治影响:

  • 共享代码的变更需要双方同意(类似开源项目的PR流程)
  • 共享部分必须非常小且稳定

代码实现:

// shared-kernel 包(独立仓库)
// money.ts
export class Money {
  constructor(
    readonly amount: bigint,  // 用最小单位避免精度问题
    readonly currency: Currency
  ) {}

  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new CurrencyMismatchError();
    }
    return new Money(this.amount + other.amount, this.currency);
  }
}

// 双方作为依赖引入:
// import { Money } from '@company/shared-kernel';

代价: 共享内核必须极其稳定,否则变更波及面大。经验法则——共享内核不超过总代码的5%。

模式3: Customer-Supplier (客户-供应商)

定义: 上游(Supplier)提供服务,下游(Customer)消费服务。上游有主导权但需要考虑下游需求。

适用场景:

  • 上游团队是平台/基础服务,下游是业务团队
  • 上游有义务响应下游的合理需求
  • 例:用户中心(上游) → 订单服务(下游)

组织政治影响:

  • 关键问题:上游的产品优先级是否包含下游的需求?
  • 如果上游不重视下游需求,关系会退化为Conformist
  • 需要管理层确保上游对下游的服务承诺

代码实现:

// 上游(用户中心)提供API,下游(订单服务)调用
// 上游维护API契约,下游可以提出需求
// 上游通过Consumer-Driven Contract Testing保障兼容性

// 上游API
GET /api/users/{userId}
Response: { id, name, email, tier }

// 下游可以提需求: "我需要user的tier字段"
// 上游评估后添加

模式4: Conformist (跟随者)

定义: 下游无条件接受上游的模型,上游不关心下游的需求。

适用场景:

  • 上游是第三方服务/外部API,你无法影响其设计
  • 例:你的系统对接银行核心系统的API、对接链上智能合约

组织政治影响:

  • 下游完全被动,上游的任何变更都可能破坏下游
  • 在大公司中,核心系统团队对外围团队往往就是这种关系

代码实现:

// 直接使用上游的数据模型,不做转换
// 风险: 上游模型变更直接影响你的业务逻辑

// 银行API返回什么,你就用什么
interface BankAccountResponse {
  acct_no: string;      // 银行的命名风格
  avail_bal: number;    // 银行的缩写
  ccy_cd: string;       // 你不得不适应
}

// 你的代码直接使用 BankAccountResponse,不转换

警告: 这是最危险的模式之一。当你发现自己在Conformist模式中,应该认真考虑是否引入ACL。

模式5: Anti-Corruption Layer (防腐层,ACL)

定义: 在下游和上游之间建立一个翻译层,将上游的模型转换为下游自己的模型,防止上游的"腐化"渗透到下游。

适用场景:

  • 上游模型很差(遗留系统)或与你的领域模型不一致
  • 需要对接多个上游并统一模型
  • 例:对接老核心系统、对接多条区块链

这是最重要的模式! 在10年金融软件经验中,80%的集成问题可以通过ACL解决。

代码实现:

// 上游(老核心系统)的丑陋模型
interface LegacyAccountDTO {
  ACCT_NO: string;
  AVAIL_BAL: string;  // 字符串表示的金额(!)
  CCY_CD: string;
  ACCT_STS: string;   // "A"=Active, "C"=Closed, "F"=Frozen
}

// ACL翻译层
class AccountAntiCorruptionLayer {
  translate(legacy: LegacyAccountDTO): Account {
    return new Account({
      accountNumber: new AccountNumber(legacy.ACCT_NO),
      balance: Money.fromString(legacy.AVAIL_BAL, legacy.CCY_CD),
      status: this.translateStatus(legacy.ACCT_STS),
    });
  }

  private translateStatus(code: string): AccountStatus {
    const mapping: Record<string, AccountStatus> = {
      'A': AccountStatus.Active,
      'C': AccountStatus.Closed,
      'F': AccountStatus.Frozen,
    };
    return mapping[code] ?? AccountStatus.Unknown;
  }
}

// 你的领域模型保持干净,不受上游污染
class Account {
  constructor(readonly props: {
    accountNumber: AccountNumber;
    balance: Money;
    status: AccountStatus;
  }) {}
}

模式6: Open Host Service (开放主机服务,OHS)

定义: 上游定义一套公开的、版本化的协议/API,供多个下游使用。

适用场景:

  • 上游是平台服务,有多个消费者
  • 需要标准化的API规范
  • 例:API网关、公开的REST API

代码实现:

// OpenAPI规范定义的公开协议
// GET /api/v2/accounts/{accountId}
// 版本化 + 文档化 + 向后兼容承诺

模式7: Published Language (发布语言,PL)

定义: 使用行业标准的数据格式进行通信。

适用场景:

  • 跨组织集成,使用行业标准协议
  • 例:金融行业的FIX协议、ISO 20022、SWIFT报文

实例:

金融领域: FIX协议(交易)、SWIFT MT/MX(支付)、ISO 20022(报文)
Web3领域: ERC-20/721/1155标准、ABI编码、JSON-RPC

模式8: Separate Ways (各行其道)

定义: 两个上下文完全不集成,各自实现所需功能。

适用场景:

  • 集成成本高于重复实现成本
  • 两个上下文的需求重叠很小
  • 例:每个微服务维护自己的用户缓存而非每次调用用户中心

何时选择:

集成成本 > 重复实现成本 + 数据一致性风险 → Separate Ways
反之 → 集成

知识点3: 上下文发现的系统方法

核心问题:如何在一个复杂系统中识别出所有的Bounded Context?

方法论:四步发现法

Step 1: 业务能力分解 (Business Capability Mapping)
  └── 列出组织的核心业务能力
  └── 每个业务能力可能是一个BC

Step 2: 语言分析 (Linguistic Analysis)
  └── 收集各团队使用的术语表
  └── 同一术语不同含义 → 不同BC
  └── 不同术语同一含义 → 可能是同一BC

Step 3: 数据流分析 (Data Flow Analysis)
  └── 追踪核心实体在系统中的流转
  └── 实体的属性在哪里发生质变 → BC边界

Step 4: 变更频率分析 (Rate of Change Analysis)
  └── 高频变更的模块和低频变更的模块应该分开
  └── 不同的变更驱动力 → 不同的BC

对比分析

8种Context Mapping关系模式对比

模式权力关系协调成本耦合度适合场景组织前提
Partnership平等最高紧密协作的兄弟团队共同OKR
Shared Kernel平等中-高少量共享概念共同代码审查
Customer-Supplier不等(上游主导)平台→业务上游有服务意识
Conformist不等(上游完全主导)高(单向)对接不可控的外部系统无话语权
ACL不等(下游自保)对接遗留系统/外部API下游有技术能力
OHS不等(上游提供标准)低-中多消费者的平台服务上游有规范能力
Published Language平等(遵循标准)跨组织集成行业标准存在
Separate Ways集成成本>收益-

常见组合模式

场景推荐组合
对接银行核心Conformist + ACL(翻译层保护自己)
平台服务暴露APIOHS + Published Language(标准协议)
新旧系统共存ACL(隔离遗留) + Customer-Supplier(新系统间)
微服务间通信Customer-Supplier(大多数) + Partnership(紧耦合的少数)

架构设计实操

实操: 为"全渠道零售银行"做完整上下文映射

业务背景: 一家零售银行,有以下业务能力:

  • 个人银行账户(开户/销户/冻结)
  • 存贷款(活期/定期/贷款)
  • 支付转账(行内转账/跨行转账/国际汇款)
  • 信用卡(申请/授信/账单/还款)
  • 投资理财(基金/保险/理财产品)
  • 风险管理(KYC/AML/信用评分)
  • 客户关系(客户360视图/营销/投诉)
  • 渠道(手机银行/网银/柜台/ATM)

Step 1: 识别Bounded Context

1. Identity & Access (身份认证)
   └── 登录/认证/授权/会话管理

2. Customer (客户管理)
   └── 客户基本信息/KYC/客户分层

3. Account (账户管理)
   └── 开户/销户/账户状态/余额

4. Deposit (存款)
   └── 活期/定期/利息计算

5. Lending (贷款)
   └── 贷款申请/审批/放款/还款/逾期

6. Payment (支付)
   └── 行内转账/跨行转账/国际汇款

7. Card (信用卡)
   └── 申请/授信/交易/账单/还款

8. Investment (投资理财)
   └── 基金/保险/理财产品销售

9. Risk (风险管理)
   └── AML/反欺诈/信用评分

10. Channel (渠道层)
    └── 手机银行/网银/柜台/API网关

11. Notification (通知)
    └── 短信/Push/邮件/站内信

12. Ledger (总账)
    └── 复式记账/对账/报表

Step 2: 绘制Context Map

Context Map:

[Channel] ──(OHS)──→ [Identity]
    │                     │
    │(OHS)           (Customer-Supplier)
    │                     │
    ├──────────→ [Customer] ←──(ACL)── [外部征信系统]
    │                │
    │           (Customer-Supplier)
    │                │
    ├──→ [Account] ←─┘
    │       │
    │    (Shared Kernel: Money)
    │       │
    │   ┌───┼───┬───────┐
    │   │   │   │       │
    │   ▼   ▼   ▼       ▼
    │ [Deposit][Lending][Card] [Investment]
    │   │       │   │       │
    │   │  (Customer-Supplier)│
    │   │       │   │       │
    │   └───┬───┘   └───┬───┘
    │       │           │
    │       ▼           │
    │   [Payment]       │
    │       │           │
    │  (Partnership)    │
    │       │           │
    │       ▼           │
    │   [Ledger] ←──────┘
    │
    └──→ [Notification] (Separate Ways - 独立的通知服务)

[Risk] ──(ACL)──→ [外部AML数据库]
  │
  (Customer-Supplier, 下游)
  │
  ├── [Lending] (上游提供贷款数据给Risk)
  ├── [Card] (上游提供信用卡数据给Risk)
  └── [Payment] (上游提供交易数据给Risk)

Step 3: 每对关系的详细说明

上游下游关系类型理由
IdentityChannelOHS多渠道接入,需要标准化认证协议
CustomerAccount/Lending/CardCustomer-Supplier客户中心提供基础数据,业务线消费
外部征信CustomerACL外部系统格式不可控,需要翻译层
Account & Deposit & Lending & Card(共享)Shared Kernel(Money)Money值对象是跨上下文的通用概念
PaymentLedgerPartnership支付和记账必须紧密协调,一致性要求极高
Lending/Card/PaymentRiskCustomer-Supplier(反向)业务线提供数据给风控消费
Notification所有Separate Ways通知是独立能力,不与业务逻辑耦合
外部AML数据库RiskACL外部数据格式和更新频率不可控

Step 4: 组织结构影响分析

根据Context Map,建议的团队拓扑:

平台团队(Stream-aligned):
├── Identity Team (2-3人)
├── Customer Team (3-4人)
├── Notification Team (2人)

业务团队(Stream-aligned):
├── Account & Deposit Team (4-5人)
├── Lending Team (5-6人)
├── Card Team (4-5人)
├── Payment & Ledger Team (5-6人)  ← Partnership关系决定了这两个BC适合同一团队
├── Investment Team (3-4人)

支撑团队(Enabling):
├── Risk & Compliance Team (4-5人)
├── Channel/Platform Engineering Team (3-4人)

ADR

# ADR-002: Payment与Ledger的Context Mapping关系选择

## 状态: 已接受

## 上下文
支付(Payment)和总账(Ledger)之间的关系需要确定。支付的每笔交易都需要
在总账中记录借贷分录。两者的数据一致性要求是金融级(不允许差错)。

## 考虑的关系模式
1. Customer-Supplier: Payment作为上游,Ledger作为下游
2. Partnership: 双方平等协作
3. Shared Kernel: 共享交易模型

## 决策
选择Partnership。

## 理由
- 支付和记账的一致性要求极高,任何接口变更都需要双方同步
- 两个领域的变更频率相似(都与业务规则变化相关)
- Customer-Supplier模式中上游可能不够重视下游需求,金融场景不可接受
- Shared Kernel范围太大(不止Money,还有Transaction Event),维护成本高
- Partnership的高协调成本在"金融一致性"面前是值得的

## 后果
- Payment和Ledger团队需要每周联合规划会
- 接口变更采用双方代码审查制度
- 建议两个团队坐在一起(物理接近降低协调成本)

AI增强实践

AI在DDD战略设计中的应用

1. 领域术语分析

Prompt: "以下是我们系统中不同团队使用的术语列表:
- 销售团队: 客户、订单、报价、合同
- 仓储团队: 订单、发货单、库存、SKU
- 财务团队: 订单、发票、收款、账目

请分析:
1. 哪些术语在不同团队中含义不同(候选的BC边界)?
2. 建议划分哪些Bounded Context?
3. 各BC之间可能的关系模式?"

2. Context Map审查

Prompt: "我画了一个Context Map,包含以下BC和关系:
[列出你的BC和关系]

请审查:
1. 是否有遗漏的BC?
2. 关系模式是否合理? 有没有该用ACL的地方用了Conformist?
3. 是否有循环依赖?
4. 团队拓扑建议?"

3. 用AI模拟业务专家

当缺乏领域专家参与时,可以让AI扮演某个领域的业务专家,进行启发式讨论。但要注意——AI的领域知识是泛化的,不能替代真正的业务专家。AI能帮你准备问题清单和初步假设,但最终验证必须由真实的业务人员完成。

AI vs 人工边界:

任务AI能力人工不可替代
术语分析和边界假设优秀真实的语言使用差异需要观察
Context Map的逻辑检查良好组织政治和权力关系
生成关系模式建议良好团队文化和协作意愿的判断
Event Storming引导有限现场引导和氛围管理

与Web3/DeFi的关联

传统架构概念Web3对应关键差异
Bounded Context独立的智能合约/协议Web3天然有"合约边界",但不等于好的BC
Context Mapping合约间调用关系(Composability)DeFi乐高本质上就是Contract Map
ACL(防腐层)协议适配层(如DEX聚合器)1inch等聚合器就是多DEX的ACL
Shared KernelERC标准(ERC-20/721)行业标准=最成功的Shared Kernel
Published LanguageABI/JSON-RPC链上的Published Language是强制的
Customer-Supplier协议依赖(如Aave依赖Chainlink预言机)上游(Chainlink)出问题,下游(Aave)受影响
Conformist无法修改的链上合约已部署的合约你只能遵从其接口
Separate Ways协议Fork如果上游不满足需求就Fork

Web3中的Context Mapping实例:

[Uniswap V3]
    │
    ├── (Conformist) → [Chainlink Price Feed]
    │   原因: Uniswap无法改变Chainlink的接口
    │
    ├── (OHS) → [各DeFi协议/前端/聚合器]
    │   原因: Uniswap提供标准化的Router接口
    │
    ├── (Published Language) → [ERC-20 Token标准]
    │   原因: 所有token遵循ERC-20
    │
    └── (Separate Ways) ← [SushiSwap]
        原因: Sushi Fork了Uniswap,各行其道

今日思考

  1. 你经历过的系统中,最痛苦的跨团队集成是什么? 用今天学的8种模式重新审视,那次集成属于什么模式?如果重新来过,你会选择什么模式?ACL是否能解决当时的问题?

  2. Conway定律是诅咒还是工具? 如果你的组织结构不合理(比如一个团队横跨了3个本应独立的BC),你会选择改变组织结构还是改变系统边界?在你的公司里,哪个更现实?

  3. DeFi的Composability是Context Mapping的极致形态吗? 在DeFi中,任何协议都可以被任何其他协议调用,这种极端的开放性带来了创新(DeFi乐高),但也带来了风险(级联清算)。从DDD视角看,DeFi缺少的是什么?


面试题准备

Q1: 如何识别限界上下文边界?

30秒版本: 我用三个启发式方法:语言边界(同一个词在不同团队含义不同)、团队边界(符合Conway定律)、变更频率边界(变更原因不同的模块应该分开)。最终验证是:拆掉这个上下文后,哪些业务流程会断?

2分钟版本:

识别BC边界是DDD战略设计的核心挑战。我的方法论:

第一,语言分析。这是Eric Evans最强调的方法。我会收集各团队的领域术语表,找出那些"同一个词但含义不同"的地方。比如在金融系统中,"账户"在认证、银行、会计三个领域的含义完全不同——这就是三个BC。

第二,团队边界。Conway定律告诉我们系统结构反映组织结构。我会对照组织架构图来校验我的BC划分。如果一个BC横跨了两个独立团队,通常意味着要么BC需要拆分,要么组织需要调整。

第三,变更驱动力分析。两个模块如果由不同的原因驱动变更(一个由监管变化驱动,一个由用户需求驱动),它们应该是不同的BC。这也是单一职责原则在战略层面的体现。

第四,验证。BC的边界不是一次画对的,需要持续验证。如果发现一个需求频繁需要同时修改多个BC,说明边界可能画错了。

追问准备:

追问: "组织政治如何影响上下文关系?" 回答: 在实际工作中,Context Mapping的关系模式很大程度上反映了组织的权力结构。比如核心系统团队对外围团队往往是Conformist关系——不是技术选择而是政治现实。好的架构师会识别这种关系并通过ACL保护自己的团队,而不是试图改变权力结构。

Q2: 什么时候应该用ACL(防腐层)?

30秒版本: 当你对接的上游系统模型质量差(遗留系统)、不可控(第三方API)、或者与你的领域模型差异大时,就应该用ACL。ACL的本质是"用翻译层保护你的领域模型的纯净性"。在我的经验中,对接遗留金融核心系统时ACL几乎是必须的。

2分钟版本:

ACL是DDD中我使用频率最高的模式。三种必须用的场景:

第一,对接遗留系统。老核心系统的数据模型往往是20年前设计的,字段命名混乱、类型不规范、语义不清。如果不加ACL,这些"腐化"会渗透到你的新系统中。

第二,对接不可控的外部API。第三方API的变更你无法控制,ACL作为缓冲层,当上游变化时只需要修改翻译层。

第三,模型差异大。当上游的"Account"和你的"Account"含义完全不同时,直接映射会导致代码中到处是转换逻辑。ACL把所有转换集中在一处。

ACL的实现要点:

  • 放在独立的模块/包中
  • 只做翻译,不加业务逻辑
  • 上游模型变化只影响ACL层,不影响下游业务代码

追问准备:

追问: "ACL的缺点是什么?" 回答: 主要是维护成本和性能开销。每次上游变更都需要同步更新ACL。对于高吞吐场景,额外的翻译层会增加延迟。所以如果上游模型和你的模型差异很小,用ACL反而是过度设计。


学习资源

  1. 《Domain-Driven Design》 - Eric Evans - Chapter 14: Context Map (战略设计的核心章节)
  2. 《Implementing Domain-Driven Design》 - Vaughn Vernon - Part 1: Strategic Design (更实操的指南)
  3. 《Team Topologies》 - Matthew Skelton & Manuel Pais - 组织结构与架构的关系
  4. Context Mapping Patterns - DDD Crew GitHub: https://github.com/ddd-crew/context-mapping
  5. Bounded Context Canvas - DDD Crew 的BC可视化工具
  6. Nick Tune的Strategic Domain-Driven Design - InfoQ演讲

明日预告

Day 10: DDD的争议与取舍 —— DDD不是银弹。我们将直面DDD的争议:聚合根边界画错了怎么办?Event Sourcing什么时候是灾难?贫血模型真的那么差吗?并分析3个真实的DDD实施失败案例,建立"何时不该用DDD"的判断框架。