微服务拆分精讲 / 第 03 章:领域驱动设计
第 03 章:领域驱动设计
DDD 不是技术,而是思维方式。它教我们用业务语言来设计系统,用限界上下文来划分边界。
3.1 DDD 核心概念
3.1.1 什么是领域驱动设计
领域驱动设计(Domain-Driven Design,DDD)是 Eric Evans 在 2003 年提出的一种软件设计方法论。它的核心思想是:
软件设计的核心复杂性在于业务领域,而非技术实现。
┌──────────────────────────────────────────────────────┐
│ DDD 分层架构 │
├──────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 用户接口层 (Interface Layer) │ │
│ │ Controller · DTO · API Gateway │ │
│ └──────────────────────┬────────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 应用层 (Application Layer) │ │
│ │ Application Service · 编排 · 事务管理 │ │
│ └──────────────────────┬────────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 领域层 (Domain Layer) ⭐ 核心 │ │
│ │ Entity · Value Object · Aggregate │ │
│ │ Domain Service · Domain Event │ │
│ └──────────────────────┬────────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────┐ │
│ │ 基础设施层 (Infrastructure Layer) │ │
│ │ Repository · ORM · MQ · External API │ │
│ └───────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
3.1.2 DDD 战略设计 vs 战术设计
| 维度 | 战略设计(Strategic) | 战术设计(Tactical) |
|---|---|---|
| 关注点 | 业务边界划分 | 代码结构设计 |
| 核心概念 | 限界上下文、上下文映射 | 聚合、实体、值对象 |
| 参与者 | 架构师 + 业务专家 | 开发团队 |
| 输出物 | 上下文地图、服务划分方案 | 领域模型代码 |
| 与微服务的关系 | 指导服务拆分 | 指导服务内部设计 |
3.1.3 统一语言(Ubiquitous Language)
统一语言是 DDD 的基石——开发团队和业务专家使用同一套术语进行沟通。
❌ 开发人员和业务人员说不同的话
开发:"我在 user 表加了一个 status 字段,0 表示未激活..."
业务:"什么 status?我们叫'客户状态',有'新注册'、'已认证'、'已冻结'..."
✅ 统一语言:双方使用相同的术语
术语表:
┌──────────────┬────────────────────────────┐
│ 术语 │ 含义 │
├──────────────┼────────────────────────────┤
│ 客户 (Customer) │ 已完成注册并通过认证的用户 │
│ 客户状态 │ 新注册 / 已认证 / 已冻结 │
│ 订单 (Order) │ 客户发起的购买请求 │
│ 订单项 (LineItem) │ 订单中的一个商品及数量 │
└──────────────┴────────────────────────────┘
💡 实践建议:在项目开始时建立术语表(Glossary),并在整个团队中推广使用。代码中的类名、方法名应该直接使用统一语言中的术语。
3.2 限界上下文(Bounded Context)
3.2.1 概念
限界上下文是 DDD 中最重要的概念,也是微服务拆分的核心依据。
限界上下文是一个边界,在这个边界内,领域模型的每个术语都有明确且唯一的含义。
┌─────────────── 电商系统 ───────────────────────┐
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 销售上下文 │ │ 物流上下文 │ │
│ │ │ │ │ │
│ │ "订单" = 购买 │ │ "订单" = 发货 │ │
│ │ 请求,包含 │ │ 指令,包含 │ │
│ │ 商品、金额 │ │ 地址、包裹 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 客户上下文 │ │ 支付上下文 │ │
│ │ │ │ │ │
│ │ "客户" = 已注册 │ │ "客户" = 付款方 │ │
│ │ 的用户,有 │ │ 有账户余额 │ │
│ │ 会员等级 │ │ 和支付方式 │ │
│ └─────────────────┘ └─────────────────┘ │
└────────────────────────────────────────────────┘
同一个词 "订单"、"客户" 在不同上下文中含义不同!
3.2.2 识别限界上下文的方法
| 方法 | 操作步骤 | 输出 |
|---|---|---|
| 事件风暴(Event Storming) | 组织工作坊,用便利贴梳理业务事件 | 领域事件序列 |
| 名词提取法 | 从业务文档中提取名词,分析聚类 | 实体/概念分组 |
| 业务流程分析 | 梳理核心业务流程,识别阶段边界 | 流程阶段划分 |
| 组织架构映射 | 参考部门/团队职责划分 | 组织-领域对应 |
3.2.3 事件风暴(Event Storming)实操
事件风暴是 Alberto Brandolini 发明的协作式建模方法:
时间轴 ──────────────────────────────────────────▶
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 🟡 命令 │ │ 🟡 命令 │ │ 🟡 命令 │ │ 🟡 命令 │
│ 提交订单 │ │ 支付订单 │ │ 发货 │ │ 确认收货 │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 🟠 事件 │ │ 🟠 事件 │ │ 🟠 事件 │ │ 🟠 事件 │
│ 订单已 │ │ 支付已 │ │ 订单已 │ │ 订单已 │
│ 提交 │ │ 完成 │ │ 发货 │ │ 完成 │
└────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 🔵 聚合 │ │ 🔵 聚合 │ │ 🔵 聚合 │ │ 🔵 聚合 │
│ 订单 │ │ 支付 │ │ 物流 │ │ 订单 │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 🟣 上下文│ │ 🟣 上下文│ │ 🟣 上下文│
│ 交易上下文│ │ 支付上下文│ │ 物流上下文│
└─────────┘ └─────────┘ └─────────┘
操作步骤:
- 召集工作坊:业务专家 + 开发团队(5-10 人)
- 梳理领域事件(橙色便利贴):用过去时描述,如"订单已提交"
- 识别命令(蓝色便利贴):触发事件的操作,如"提交订单"
- 标注聚合(黄色便利贴):处理命令的核心实体
- 划定边界(粉色线):将相关事件/聚合归入同一上下文
3.2.4 上下文映射(Context Mapping)
不同限界上下文之间的关系模式:
| 映射模式 | 英文 | 说明 | 示例 |
|---|---|---|---|
| 合作关系 | Partnership | 两个上下文紧密合作,共同演进 | 用户服务 ↔ 会员服务 |
| 共享内核 | Shared Kernel | 两个上下文共享部分模型 | 共享用户基础信息 |
| 客户-供应商 | Customer-Supplier | 上游提供,下游消费 | 商品服务(供)→ 订单服务(客) |
| 防腐层 | Anti-Corruption Layer | 通过翻译层隔离外部模型 | 遗留系统接入层 |
| 开放主机服务 | Open Host Service | 提供标准化协议供多方使用 | 开放 API |
| 遵从者 | Conformist | 下游完全遵从上游模型 | 使用第三方 SDK |
| 各行其道 | Separate Ways | 两个上下文完全独立 | 推荐系统 ↔ 客服系统 |
┌──────────────┐ 客户-供应商 ┌──────────────┐
│ 商品上下文 │ ──────────────▶ │ 订单上下文 │
│ (Supplier) │ │ (Customer) │
└──────────────┘ └──────┬───────┘
│
防腐层│(ACL)
▼
┌──────────────┐
│ 物流上下文 │
│ (Legacy系统) │
└──────────────┘
3.3 聚合根(Aggregate Root)
3.3.1 概念
聚合(Aggregate)是一组相关对象的集合,作为数据变更的一致性边界。聚合根(Aggregate Root)是聚合的入口点,外部只能通过聚合根访问聚合内部的对象。
┌──────────────────────────────────────────────┐
│ 聚合:订单 (Order) │
│ 聚合根:Order │
│ │
│ ┌──────────┐ │
│ │ Order │ ◀── 外部只能通过 Order 访问 │
│ │ (聚合根) │ │
│ └────┬─────┘ │
│ │ │
│ ├──── OrderItem (实体) │
│ │ ├─ productId │
│ │ ├─ quantity │
│ │ └─ price │
│ │ │
│ ├──── ShippingAddress (值对象) │
│ │ ├─ province │
│ │ ├─ city │
│ │ └─ detail │
│ │ │
│ └──── PaymentInfo (值对象) │
│ ├─ method │
│ └─ transactionId │
└──────────────────────────────────────────────┘
3.3.2 聚合设计原则
| 原则 | 说明 | 违反后果 |
|---|---|---|
| 聚合边界保护一致性 | 聚合内的事务必须强一致 | 数据不一致 |
| 通过 ID 引用其他聚合 | 聚合之间不直接持有对象引用 | 聚合边界模糊 |
| 一个事务只修改一个聚合 | 跨聚合操作通过领域事件 | 分布式事务难题 |
| 聚合根控制访问 | 外部不能直接修改聚合内部对象 | 一致性失控 |
| 小聚合优先 | 聚合尽量小,只包含必要元素 | 性能和并发问题 |
3.3.3 聚合设计实例
❌ 错误:聚合过大
┌──────────────────────────────────────────┐
│ Order (聚合根) │
│ ├─ OrderItems[] │
│ ├─ Customer (整个客户对象) ← 不应该! │
│ │ ├─ name, email │
│ │ ├─ addresses[] │
│ │ ├─ paymentMethods[] │
│ │ └─ membershipLevel │
│ └─ Product (整个商品对象) ← 不应该! │
│ ├─ name, description │
│ ├─ images[] │
│ └─ inventory │
└──────────────────────────────────────────┘
✅ 正确:通过 ID 引用
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Order │ │ Customer │ │ Product │
│ (聚合根) │ │ (聚合根) │ │ (聚合根) │
│ │ │ │ │ │
│ customerId──┼──▶ │ id, name │ │ id, name │
│ │ │ email │ │ price │
│ items[] │ │ │ │ │
│ ├ productId─┼──▶ │ │ │ │
│ ├ quantity │ └─────────────┘ └─────────────┘
│ └ price │
│ address │
└─────────────┘
3.4 领域事件(Domain Event)
3.4.1 概念
领域事件表示领域中发生的、业务专家关心的事情。它是聚合之间、限界上下文之间通信的主要方式。
命令 (Command) 领域事件 (Domain Event)
───────────── ────────────────────
提交订单 ──▶ OrderSubmitted
支付完成 ──▶ PaymentCompleted
商品已发货 ──▶ OrderShipped
库存不足 ──▶ StockInsufficient
3.4.2 领域事件命名规范
| 规范 | ✅ 正确 | ❌ 错误 |
|---|---|---|
| 使用过去时 | OrderSubmitted | SubmitOrder / OrderSubmit |
| 业务语言 | PaymentCompleted | PaymentStatusChanged |
| 具体而非笼统 | OrderCancelled | DataModified |
| 不含技术术语 | CustomerRegistered | UserInsertedToDB |
3.4.3 领域事件驱动的服务协作
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 订单服务 │ │ 支付服务 │ │ 库存服务 │
│ │ │ │ │ │
│ 创建订单 │ │ │ │ │
│ │ │ │ │ │ │
│ ▼ │ │ │ │ │
│ 发布事件: │ │ │ │ │
│ Order │ │ │ │ │
│ Submitted│ │ │ │ │
└────┬─────┘ └──────────┘ └──────────┘
│ │
│ ┌──── 消息队列 ────┐ │
├─────────▶│ OrderSubmitted │◀──────────┤
│ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ 支付服务 │ │
│ │ 创建支付 │ │
│ │ 记录 │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ 发布事件: │
│ Payment │
│ Completed │
│ │ │
│ ▼ ▼
│ ┌──────────┐ ┌──────────┐
│ │ 订单服务 │ │ 库存服务 │
│ │ 更新订单 │ │ 扣减库存 │
│ │ 状态 │ │ │
│ └──────────┘ └──────────┘
3.4.4 事件的结构
{
"eventId": "evt-20260510-001",
"eventType": "OrderSubmitted",
"timestamp": "2026-05-10T10:30:00+08:00",
"source": "order-service",
"data": {
"orderId": "ORD-20260510-001",
"customerId": "CUST-001",
"totalAmount": 299.00,
"items": [
{
"productId": "PROD-001",
"quantity": 2,
"price": 149.50
}
]
},
"metadata": {
"correlationId": "req-abc-123",
"version": "1.0"
}
}
3.5 实体(Entity)与值对象(Value Object)
3.5.1 区别
| 维度 | 实体 (Entity) | 值对象 (Value Object) |
|---|---|---|
| 标识 | 有唯一 ID | 没有 ID,通过属性值识别 |
| 可变性 | 可变 | 不可变 |
| 相等性 | ID 相同即相等 | 所有属性相同才相等 |
| 生命周期 | 有独立生命周期 | 依附于实体 |
| 示例 | Order, Customer, Product | Money, Address, DateRange |
实体 (Entity)
┌────────────────────────┐
│ Order │
│ id: "ORD-001" ← 唯一标识 │
│ status: PAID │ ← 可变
│ createdAt: 2026-05-10 │
└────────────────────────┘
值对象 (Value Object)
┌────────────────────────┐
│ Money │
│ amount: 100.00 │ ← 不可变
│ currency: "CNY" │
│ │
│ Money(100, "CNY") │
│ == Money(100, "CNY") │ ← 值相等即相等
└────────────────────────┘
3.5.2 代码示例
// 实体 - 有唯一标识
public class Order {
private String orderId; // 唯一标识
private OrderStatus status;
private List<OrderItem> items;
private Money totalAmount; // 值对象
public void cancel() {
if (this.status == OrderStatus.SHIPPED) {
throw new BusinessException("已发货订单不能取消");
}
this.status = OrderStatus.CANCELLED;
}
}
// 值对象 - 不可变,无标识
public record Money(BigDecimal amount, String currency) {
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("币种不同不能相加");
}
return new Money(this.amount.add(other.amount), this.currency);
}
}
3.6 从业务场景到服务划分
3.6.1 完整案例:外卖平台
第一步:事件风暴梳理业务流程
用户浏览餐厅 → 用户选择菜品 → 用户下单 → 餐厅接单 → 骑手取餐 → 骑手送达 → 用户评价
对应的领域事件:
CustomerBrowsed → ItemSelected → OrderPlaced → RestaurantAccepted
→ RiderAssigned → FoodPickedUp → FoodDelivered → OrderReviewed
第二步:识别限界上下文
| 上下文 | 包含的聚合 | 核心职责 |
|---|---|---|
| 用户上下文 | Customer, Address | 用户注册、认证、地址管理 |
| 餐厅上下文 | Restaurant, Menu, Dish | 餐厅入驻、菜单管理 |
| 订单上下文 | Order, OrderItem | 下单、订单状态管理 |
| 配送上下文 | Rider, Delivery | 骑手管理、配送调度 |
| 支付上下文 | Payment, Refund | 支付处理、退款 |
| 评价上下文 | Review, Rating | 用户评价 |
第三步:映射为微服务
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 用户服务 │ │ 餐厅服务 │ │ 订单服务 │
│ │ │ │ │ │
│ Customer │ │ Restaurant│ │ Order │
│ Address │ │ Menu │ │ OrderItem│
│ │ │ Dish │ │ │
└──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 配送服务 │ │ 支付服务 │ │ 评价服务 │
│ │ │ │ │ │
│ Rider │ │ Payment │ │ Review │
│ Delivery │ │ Refund │ │ Rating │
└──────────┘ └──────────┘ └──────────┘
3.6.2 上下文协作流程
用户下单的完整流程:
用户服务 订单服务 支付服务 餐厅服务 配送服务
│ │ │ │ │
│ 创建订单 │ │ │ │
│──────────────▶│ │ │ │
│ │ │ │ │
│ │ 创建支付 │ │ │
│ │──────────────▶│ │ │
│ │ │ │ │
│ │ PaymentCompleted │ │
│ │◀──────────────│ │ │
│ │ │ │ │
│ │ OrderPaid │ │ │
│ │──────────────────────────────▶│ │
│ │ │ │ │
│ │ │ RestaurantAccepted │
│ │◀──────────────────────────────│ │
│ │ │ │ │
│ │ OrderReady │ │ │
│ │──────────────────────────────────────────────▶│
│ │ │ │ │
│ │ │ │ DeliveryDone │
│ │◀──────────────────────────────────────────────│
│ │ │ │ │
│ OrderCompleted │ │ │
│◀──────────────│ │ │ │
3.7 DDD 工具与框架
| 语言/平台 | 框架 | 说明 |
|---|---|---|
| Java | Axon Framework | CQRS + Event Sourcing 框架 |
| Java | Spring Modulith | 模块化单体 + DDD |
| .NET | eShopOnContainers | 微服务参考实现 |
| Go | go-ddd-skeleton | DDD 项目骨架 |
| Python | eventsourcing | 事件溯源库 |
⚠️ 注意事项
- DDD 不是万能的——简单 CRUD 业务不需要 DDD
- 限界上下文 ≠ 微服务——一个限界上下文可以包含多个服务
- 不要跳过事件风暴——直接画技术架构图会遗漏业务细节
- 聚合设计要谨慎——聚合过大导致性能问题,过小导致分布式事务
- 统一语言要落地——代码中的命名必须与统一语言一致
📖 扩展阅读
- Eric Evans - Domain-Driven Design: Tackling Complexity in the Heart of Software — DDD 圣经
- Vaughn Vernon - Implementing Domain-Driven Design — DDD 实践指南
- Alberto Brandolini - Introducing EventStorming — 事件风暴方法论
- Martin Fowler - BoundedContext — 限界上下文详解
- Chris Richardson - Microservices Patterns, Chapter 4 — DDD 与微服务结合
本章小结
| 要点 | 说明 |
|---|---|
| DDD 核心 | 以业务领域为中心设计软件 |
| 限界上下文 | 微服务拆分的核心依据 |
| 聚合根 | 数据一致性的边界 |
| 领域事件 | 服务间通信的推荐方式 |
| 事件风暴 | 识别上下文的协作式方法 |
📌 下一章:第 04 章:拆分策略 — 将 DDD 理论落地为可操作的拆分策略。