3. 基于DDD的限界上下文划分
限界上下文(Bounded Context)是领域驱动设计(DDD)中最核心的战略设计工具,它直接回答了“某个领域概念应该由哪个模块负责”这一经典问题。本章将围绕子领域识别、上下文映射、聚合与实体边界、共享内核四个环节,给出可操作的划分步骤与权衡策略。
3.1 子领域识别:从业务域到模型边界
子领域(Subdomain)是识别限界上下文的前置步骤。核心子领域决定系统竞争力,支撑子领域承载通用能力,通用子领域可直接采购或复用。通过事件风暴(Event Storming)或业务流分析,将整个业务域拆解为多个互不重叠的子领域,每个子领域对应一个潜在上下文。
| 子领域类型 | 特征 | 上下文划分策略 |
|---|---|---|
| 核心 | 复杂、高商业价值、差异性强 | 专属上下文,封闭演进,高质量团队投入 |
| 支撑 | 必要但非核心,可内部开发 | 可独立上下文,也可与主上下文共享部分模型 |
| 通用 | 标准化,如认证、通知 | 采购或开源产品,通过防腐层隔离 |
3.2 上下文映射图:明确协作关系
上下文映射图(Context Map)描述了限界上下文之间的通信方式与信任等级。常见关系包括:合作关系(Partnership)、共享内核(Shared Kernel)、客户-供应商(Customer-Supplier)、防腐层(Anti-Corruption Layer)等。选择关系类型取决于两个上下文之间的耦合度与演化自由度。
- 合作关系:两个团队同步计划,频繁集成,适用于早期探索阶段。
- 客户-供应商:上游定义接口,下游按需实现,适用于稳定依赖场景。
- 防腐层:通过适配器隔离外部上下文的模型变化,适用于集成遗留系统或第三方。
- 分离方式:无直接调用,通过事件异步通信,最松散耦合。
3.3 聚合与实体边界:划出内聚单元
聚合(Aggregate)是限界上下文内部的数据修改单元。每个聚合由根实体(Aggregate Root)控制所有内部实体的访问与持久化,外部只能通过根进行操作。聚合边界决定了事务边界与一致性范围。
划分聚合时遵循三条规则:
1. 根实体全局唯一:使用UUID或前缀+ID,不在不同上下文中混用。
2. 一次事务只修改一个聚合:跨聚合改动用最终一致性(事件驱动)。
3. 聚合尽量小:理想状态下包含1~3个实体,避免加载大量无关数据。
3.4 共享内核:高收益与高风险并存
共享内核(Shared Kernel)指两个上下文共享一部分领域模型与代码。这是DDD中耦合度最高的一种关系,适用于“两个上下文必须使用完全相同的概念定义”的场景,如订单上下文与支付上下文共享金额对象。
共享内核的落地要求:
- 双方团队共同维护共享模块,变更需双方评审。
- 共享部分必须使用独立模块或仓库,不允许直接复制代码。
- 尽可能将共享内容限制为值对象与接口,避免共享实体生命周期。
// 共享内核示例:金额值对象(放在shared-kernel模块中)
public record Money(Currency currency, BigDecimal amount) {
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new DomainException("货币不匹配");
}
return new Money(this.currency, this.amount.add(other.amount));
}
}
// 订单上下文中使用
public class OrderItem {
private Money unitPrice;
private int quantity;
public Money subtotal() {
return unitPrice.multiply(quantity);
}
}
// 支付上下文也使用同一Money对象
public class Payment {
private Money totalAmount;
public boolean canProcess(Money balance) {
return balance.amount().compareTo(totalAmount.amount()) >= 0;
}
}
3.5 落地步骤与验收标准
落地五步法
- 事件风暴产出事件流:与业务方一起梳理核心业务流程,标记每个事件的归属子领域。
- 映射上下文:将子领域分组为候选上下文,用上下文映射图记录关系。
- 识别聚合边界:对每个上下文,从根实体出发逐步内聚,直到实体间的非事务依赖被消除。
- 抽取共享内核:确认哪些模型必须完全一致,放入独立模块,并制定同步计划。
- 定义接口契约:使用OpenAPI或Protobuf明确定义上下文间的数据交换。
验收标准
| 维度 | 通过条件 |
|---|---|
| 边界清晰度 | 每个上下文拥有唯一的通用语言词汇表,无二义性条目 |
| 独立部署 | 修改一个上下文的代码无需同时部署其他上下文 |
| 事务一致性 | 同一聚合内的数据修改在一个事务内完成,跨聚合通过事件最终一致 |
| 数据库独立 | 不同上下文的数据表或模式完全隔离 |
| 共享内核稳定性 | 共享模块的接口变更频率低于每个月1次,且变更前双方预演 |
3.6 选型权衡与风险防范
在限界上下文划分过程中,以下权衡点直接影响最终架构质量:
| 决策项 | 推荐方案 | 备选方案 | 适用场景 |
|---|---|---|---|
| 上下文粒度 | 按业务能力拆分(一个上下文对应一组完整用例) | 按数据实体拆分(每个聚合独立上下文) | 核心业务用能力拆分,通用业务用实体拆分 |
| 通信模式 | 事件驱动(RabbitMQ/Kafka) | Feign REST同步调用 | 跨上下文查询用REST,命令用事件 |
| 共享模块组织 | 独立Git仓库 + 私有NPM/Maven包 | Monorepo中共享目录 | 团队协作频繁用独立仓库,小型项目用Monorepo |
| 数据库隔离 | 每上下文独立Schema + 独立数据库实例 | 同一实例不同Schema | 合规要求高用实例隔离,成本敏感用Schema |