什么是领域驱动设计(DDD)?

领域驱动设计(Domain-Driven Design,简称DDD)是一种软件开发方法论,由Eric Evans在2003年出版的同名书籍中首次提出。DDD强调以业务领域为核心驱动软件设计,通过建立清晰的领域模型来应对复杂的业务逻辑。

DDD的起源和背景

在传统的软件开发中,我们常常遇到这样的问题:

1
2
3
4
5
6
7
8
9
开发人员:这个功能应该怎么实现?
产品经理:用户点击按钮后,系统要...
开发人员:好的,我把逻辑写在Controller里
产品经理:不对,业务规则是...
开发人员:哦,那我加个if判断

几个月后...
产品经理:这个需求要改一下
开发人员:😭 这里改了,那里也要改,逻辑散落在各处...

问题的本质:

  • 业务逻辑散落在代码各处(Controller、Service、Util)
  • 代码结构以技术分层为导向,而非业务逻辑
  • 领域专家(业务人员)和开发人员之间存在沟通鸿沟
  • 随着业务复杂度增加,代码越来越难以维护

Eric Evans提出DDD,核心思想是:让软件模型直接反映业务领域,让代码说业务语言

为什么需要DDD?

传统三层架构的问题

1
2
3
4
5
6
7
8
9
// 传统三层架构:以技术分层为导向
├── Controller 层 (接收请求)
│ └── OrderController.java
├── Service 层 (业务逻辑)
│ └── OrderService.java
├── DAO 层 (数据访问)
│ └── OrderDao.java
└── Model 层 (数据模型)
└── Order.java

问题1:贫血模型 - Model 只有getter/setter,没有业务逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 贫血的订单模型
public class Order {
private Long id;
private BigDecimal totalAmount;
private String status;

// 只有getter/setter,没有任何业务行为
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ... 更多getter/setter
}

// 所有业务逻辑都在Service层
@Service
public class OrderService {
public void createOrder(Order order) {
// 验证订单
if (order.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("订单金额必须大于0");
}

// 计算折扣
if (order.getUserLevel().equals("VIP")) {
order.setTotalAmount(order.getTotalAmount().multiply(new BigDecimal("0.9")));
}

// 更新库存
inventoryService.deductStock(order.getItems());

// 保存订单
orderDao.save(order);

// 发送通知
notificationService.sendOrderConfirmation(order);
}
}

问题2:业务逻辑散落

  • 订单验证逻辑在Service层
  • 金额计算逻辑在Service层
  • 状态转换逻辑在Service层
  • 当需求变化时,要在多处修改代码

问题3:缺乏业务语义

  • 代码无法直观反映业务规则
  • 新人接手需要花大量时间理解业务

DDD的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
// DDD:以业务领域为导向
├── 订单上下文 (Order Context)
│ ├── 领域层 (Domain Layer)
│ │ ├── Order.java (订单聚合根 - 充血模型)
│ │ ├── OrderItem.java (订单项实体)
│ │ ├── Money.java (金额值对象)
│ │ ├── OrderStatus.java (订单状态值对象)
│ │ └── OrderRepository.java (订单仓储接口)
│ ├── 应用层 (Application Layer)
│ │ └── OrderApplicationService.java (应用服务)
│ └── 基础设施层 (Infrastructure Layer)
│ └── OrderRepositoryImpl.java (仓储实现)

充血的订单模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 充血模型:业务逻辑在领域对象内部
public class Order {
private OrderId id;
private Money totalAmount;
private OrderStatus status;
private List<OrderItem> items;
private UserId userId;

// 业务行为:创建订单
public static Order create(UserId userId, List<OrderItem> items) {
// 业务规则验证
if (items == null || items.isEmpty()) {
throw new OrderException("订单至少包含一个商品");
}

// 计算总金额
Money totalAmount = Money.ZERO;
for (OrderItem item : items) {
totalAmount = totalAmount.add(item.getSubtotal());
}

if (totalAmount.isLessThanOrEqualTo(Money.ZERO)) {
throw new OrderException("订单金额必须大于0");
}

Order order = new Order();
order.id = OrderId.generate();
order.userId = userId;
order.items = items;
order.totalAmount = totalAmount;
order.status = OrderStatus.PENDING;

// 发布领域事件
DomainEventPublisher.publish(new OrderCreatedEvent(order.id));

return order;
}

// 业务行为:支付订单
public void pay(Money paymentAmount) {
// 业务规则:只有待支付的订单才能支付
if (!this.status.equals(OrderStatus.PENDING)) {
throw new OrderException("订单状态不允许支付");
}

// 业务规则:支付金额必须等于订单金额
if (!paymentAmount.equals(this.totalAmount)) {
throw new OrderException("支付金额与订单金额不符");
}

this.status = OrderStatus.PAID;

// 发布领域事件
DomainEventPublisher.publish(new OrderPaidEvent(this.id, paymentAmount));
}

// 业务行为:取消订单
public void cancel(String reason) {
// 业务规则:已支付的订单不能直接取消,需要走退款流程
if (this.status.equals(OrderStatus.PAID)) {
throw new OrderException("已支付订单不能取消,请申请退款");
}

if (this.status.equals(OrderStatus.CANCELLED)) {
throw new OrderException("订单已取消");
}

this.status = OrderStatus.CANCELLED;

// 发布领域事件
DomainEventPublisher.publish(new OrderCancelledEvent(this.id, reason));
}

// 业务行为:应用折扣
public void applyDiscount(DiscountPolicy discountPolicy) {
Money discountAmount = discountPolicy.calculate(this);
this.totalAmount = this.totalAmount.subtract(discountAmount);
}
}

对比优势:

  1. 业务逻辑内聚: 订单相关的所有业务规则都在Order类内部
  2. 清晰的业务语义: order.pay()orderService.updateOrderStatus(order, "PAID") 更能表达业务意图
  3. 不变性保护: 业务规则在领域对象内部强制执行,外部无法绕过
  4. 易于测试: 领域对象的业务逻辑可以独立测试,无需依赖数据库

DDD解决什么问题?

  1. 复杂业务建模

    • 将复杂的业务领域分解为多个限界上下文
    • 每个上下文专注于特定的业务能力
    • 清晰的边界避免概念混淆
  2. 团队协作

    • 建立统一语言(Ubiquitous Language)
    • 业务人员和技术人员使用相同的术语
    • 减少沟通成本和理解偏差
  3. 代码可维护性

    • 业务逻辑集中在领域层
    • 变更业务规则时,只需修改领域对象
    • 技术实现与业务逻辑解耦
  4. 应对需求变化

    • 领域模型直接反映业务规则
    • 业务变化时,代码结构清晰易改
    • 降低维护成本

DDD vs 传统三层架构

维度 传统三层架构 DDD架构
设计导向 以技术分层为导向 以业务领域为导向
领域模型 贫血模型(只有数据) 充血模型(数据+行为)
业务逻辑 集中在Service层 集中在Domain层
代码组织 按技术层次划分 按业务上下文划分
复杂度应对 容易混乱 清晰的边界和职责
适用场景 简单CRUD系统 复杂业务系统
学习成本 较高

何时使用DDD?

适合使用DDD:

  • 业务逻辑复杂,规则多变
  • 需要长期维护的系统
  • 团队规模较大,需要清晰的边界
  • 微服务架构,需要划分服务边界

不适合使用DDD:

  • 简单的CRUD应用
  • 原型项目或短期项目
  • 技术驱动型项目(如工具类系统)
  • 团队对DDD理解不足

DDD的核心概念

DDD分为**战略设计(Strategic Design)战术设计(Tactical Design)**两部分。

战略设计:如何划分业务领域

战略设计关注的是宏观层面的架构设计,即如何将复杂的业务领域拆分为多个子域,以及如何定义它们之间的关系。

1. 领域(Domain)

**领域(Domain)**是指一个组织所从事的业务范围和活动范围。

示例:

  • 电商领域:商品、订单、支付、物流、营销等
  • 银行领域:账户、转账、贷款、理财等
  • 医疗领域:挂号、诊断、开药、收费等

一个领域通常会很大,包含多个业务模块,因此需要进一步拆分。

2. 子域(Subdomain)

将一个大的领域(Domain)拆分为多个子域(Subdomain),每个子域专注于特定的业务能力。

子域分为三类:

核心域(Core Domain)
  • 组织的核心竞争力所在
  • 直接创造业务价值
  • 需要投入最多资源
  • 必须自己开发,不能外包

示例:

1
2
3
4
电商系统的核心域:
├── 商品推荐算法 (差异化竞争力)
├── 会员体系 (用户粘性)
└── 促销引擎 (营销能力)
支撑域(Supporting Domain)
  • 支撑核心域运转
  • 不直接创造价值,但必不可少
  • 可以自己开发

示例:

1
2
3
4
电商系统的支撑域:
├── 库存管理
├── 订单管理
└── 用户管理
通用域(Generic Domain)
  • 通用性的功能
  • 不是业务特有的
  • 建议使用第三方产品或开源方案

示例:

1
2
3
4
5
电商系统的通用域:
├── 支付(使用支付宝/微信支付)
├── 物流(使用第三方物流)
├── 短信(使用阿里云短信)
└── 认证(使用OAuth2)

子域划分示例:电商系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
电商领域(E-Commerce Domain)

├── 核心域(Core Domain)
│ ├── 商品推荐子域 (Recommendation Subdomain)
│ ├── 会员权益子域 (Membership Subdomain)
│ └── 营销活动子域 (Marketing Subdomain)

├── 支撑域(Supporting Domain)
│ ├── 商品管理子域 (Product Subdomain)
│ ├── 订单管理子域 (Order Subdomain)
│ ├── 库存管理子域 (Inventory Subdomain)
│ └── 用户管理子域 (User Subdomain)

└── 通用域(Generic Domain)
├── 支付子域 (Payment Subdomain)
├── 物流子域 (Logistics Subdomain)
├── 短信通知子域 (Notification Subdomain)
└── 认证授权子域 (Authentication Subdomain)

3. 限界上下文(Bounded Context)

限界上下文(Bounded Context)是DDD中最重要的战略模式,它定义了一个明确的边界,在这个边界内,所有的术语、概念、规则都有明确且一致的含义。

为什么需要限界上下文?

同一个概念在不同的业务场景下,含义可能完全不同:

示例:”商品”的不同含义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在商品上下文中:
├── 商品(Product)
│ ├── SKU (库存单元)
│ ├── 价格
│ ├── 库存
│ └── 商品属性

在订单上下文中:
├── 商品(Product)
│ ├── 商品ID
│ ├── 购买时的价格 (下单时的快照)
│ ├── 购买数量
│ └── 小计金额

在推荐上下文中:
├── 商品(Product)
│ ├── 商品ID
│ ├── 点击率
│ ├── 转化率
│ ├── 用户评分
│ └── 推荐权重

可以看到,”商品”在三个上下文中的属性和关注点完全不同。如果没有明确的边界,就会导致:

  • 概念混淆:不清楚商品的价格是当前价还是下单时的价格
  • 代码耦合:修改商品管理影响到订单系统
  • 难以维护:一个”商品”类承载了过多职责

限界上下文的关键特征:

  1. 统一语言(Ubiquitous Language): 上下文内使用一致的术语
  2. 明确边界: 清楚定义哪些属于这个上下文,哪些不属于
  3. 独立演化: 每个上下文可以独立开发和部署
  4. 隔离性: 一个上下文的变化不影响其他上下文

限界上下文划分示例:电商系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
电商系统

├── 商品上下文(Product Context)
│ ├── 商品(Product)
│ ├── 分类(Category)
│ ├── 品牌(Brand)
│ └── SKU

├── 订单上下文(Order Context)
│ ├── 订单(Order)
│ ├── 订单项(OrderItem) <-- 商品快照
│ ├── 收货地址(Address)
│ └── 发票(Invoice)

├── 库存上下文(Inventory Context)
│ ├── 库存(Stock)
│ ├── 仓库(Warehouse)
│ └── 库存流水(StockLog)

├── 支付上下文(Payment Context)
│ ├── 支付单(Payment)
│ ├── 支付方式(PaymentMethod)
│ └── 退款(Refund)

└── 会员上下文(Member Context)
├── 会员(Member)
├── 会员等级(MemberLevel)
└── 积分(Points)

代码示例:不同上下文中的”商品”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 商品上下文:关注商品的基本信息和管理
package com.ecommerce.product.domain;

public class Product {
private ProductId id;
private String name;
private String description;
private Category category;
private Brand brand;
private Price currentPrice; // 当前销售价
private List<SKU> skus;
private ProductStatus status;

// 商品管理的业务行为
public void updatePrice(Price newPrice) {
// 更新价格的业务规则
}

public void publish() {
// 上架商品
}

public void offsale() {
// 下架商品
}
}

// 订单上下文:关注下单时的商品快照
package com.ecommerce.order.domain;

public class OrderItem {
private OrderItemId id;
private ProductId productId; // 商品ID(引用)
private String productName; // 商品名称快照
private Money snapshotPrice; // 下单时的价格快照(不受商品调价影响)
private int quantity; // 购买数量
private Money subtotal; // 小计金额

// 订单项的业务行为
public Money calculateSubtotal() {
return snapshotPrice.multiply(quantity);
}
}

// 推荐上下文:关注商品的推荐指标
package com.ecommerce.recommendation.domain;

public class RecommendationProduct {
private ProductId productId;
private double clickRate; // 点击率
private double conversionRate; // 转化率
private double userRating; // 用户评分
private int salesVolume; // 销量
private double recommendationScore; // 推荐分数

// 推荐算法相关的业务行为
public double calculateScore(UserProfile userProfile) {
// 根据用户画像计算推荐分数
}
}

4. 上下文映射(Context Map)

**上下文映射(Context Map)**定义了不同限界上下文之间的关系和集成方式。

常见的上下文关系模式:

共享内核(Shared Kernel)

两个上下文共享一部分领域模型

1
2
3
订单上下文 ←─[共享]─→ 支付上下文

共享内核:订单ID、金额
客户-供应商(Customer-Supplier)

供应商提供服务,客户依赖供应商

1
2
3
订单上下文(客户) → 商品上下文(供应商)

商品信息API
防腐层(Anti-Corruption Layer,ACL)

使用适配器隔离外部系统,避免污染自己的领域模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 防腐层:隔离第三方支付系统
public interface PaymentGateway {
PaymentResult pay(PaymentRequest request);
}

// 适配器:将第三方API适配为领域模型
@Component
public class AlipayAdapter implements PaymentGateway {
@Autowired
private AlipayClient alipayClient; // 第三方SDK

@Override
public PaymentResult pay(PaymentRequest request) {
// 转换领域模型到第三方API格式
AlipayTradePayRequest alipayRequest = new AlipayTradePayRequest();
alipayRequest.setOutTradeNo(request.getOrderId());
alipayRequest.setTotalAmount(request.getAmount().toString());

// 调用第三方API
AlipayTradePayResponse response = alipayClient.execute(alipayRequest);

// 转换第三方响应到领域模型
return PaymentResult.builder()
.success(response.isSuccess())
.transactionId(response.getTradeNo())
.build();
}
}
开放主机服务(Open Host Service)

为外部系统提供标准化的API

1
2
3
4
5
6
7
8
9
// 订单上下文对外提供RESTful API
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping("/{orderId}")
public OrderDTO getOrder(@PathVariable String orderId) {
// 将领域对象转换为DTO,对外提供标准接口
}
}

战术设计:如何实现业务领域

战术设计关注的是微观层面的代码设计,即如何用代码实现领域模型。

1. 实体(Entity)

**实体(Entity)**是具有唯一标识的对象,即使属性完全相同,只要标识不同,就是不同的实体。

核心特征:

  • 有唯一标识(ID)
  • 可变性(属性可以改变,但标识不变)
  • 连续性(生命周期内保持身份一致)

判断标准:

1
2
3
4
5
6
两个对象是否相等,取决于标识而非属性

用户A:ID=1, name="张三", age=25
用户B:ID=1, name="张三", age=26 <-- 年龄变了,但还是同一个用户

用户C:ID=2, name="张三", age=25 <-- 属性完全相同,但是不同的用户

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 订单实体
public class Order {
private OrderId id; // 唯一标识
private UserId userId;
private List<OrderItem> items;
private Money totalAmount;
private OrderStatus status;
private LocalDateTime createdAt;

// 构造函数
private Order() {}

// 工厂方法
public static Order create(UserId userId, List<OrderItem> items) {
Order order = new Order();
order.id = OrderId.generate(); // 生成唯一标识
order.userId = userId;
order.items = items;
order.totalAmount = calculateTotal(items);
order.status = OrderStatus.PENDING;
order.createdAt = LocalDateTime.now();
return order;
}

// equals 和 hashCode 基于唯一标识
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Order)) return false;
Order order = (Order) o;
return Objects.equals(id, order.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}
}

// 唯一标识对象
public class OrderId {
private final String value;

private OrderId(String value) {
this.value = Objects.requireNonNull(value, "订单ID不能为空");
}

public static OrderId generate() {
return new OrderId(UUID.randomUUID().toString());
}

public static OrderId of(String value) {
return new OrderId(value);
}

public String getValue() {
return value;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof OrderId)) return false;
OrderId orderId = (OrderId) o;
return Objects.equals(value, orderId.value);
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}

2. 值对象(Value Object)

值对象(Value Object)是没有唯一标识的对象,完全由属性值决定其身份,强调不可变性

核心特征:

  • 无唯一标识
  • 不可变(Immutable)
  • 可替换
  • 值相等(属性相同则对象相等)

判断标准:

1
2
3
4
5
两个对象是否相等,取决于所有属性值是否相同

金额A:100元
金额B:100元 <-- 属性相同,就是相同的金额,可以互换
金额C:200元 <-- 属性不同,是不同的金额

为什么要用值对象?

不使用值对象:

1
2
3
4
5
6
7
8
public class Order {
private BigDecimal totalAmount; // 用基本类型表示金额

public void applyDiscount(BigDecimal discountPercent) {
// 业务逻辑散落,容易出错
this.totalAmount = totalAmount.multiply(BigDecimal.ONE.subtract(discountPercent));
}
}

使用值对象:

1
2
3
4
5
6
7
8
public class Order {
private Money totalAmount; // 用值对象表示金额

public void applyDiscount(Percentage discount) {
// 业务逻辑封装在值对象内部
this.totalAmount = totalAmount.discount(discount);
}
}

值对象的优势:

  1. 类型安全: 不会混淆金额和数量
  2. 业务语义: MoneyBigDecimal 更能表达业务含义
  3. 不变性: 线程安全,可以安全共享
  4. 业务逻辑内聚: 金额相关的计算逻辑都在 Money 类内部

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// 金额值对象
public class Money {
private final BigDecimal amount;
private final String currency;

public static final Money ZERO = new Money(BigDecimal.ZERO, "CNY");

// 私有构造函数,确保不可变
private Money(BigDecimal amount, String currency) {
if (amount == null) {
throw new IllegalArgumentException("金额不能为空");
}
if (amount.scale() > 2) {
throw new IllegalArgumentException("金额最多保留两位小数");
}
this.amount = amount;
this.currency = currency;
}

// 工厂方法
public static Money of(BigDecimal amount, String currency) {
return new Money(amount, currency);
}

public static Money yuan(double amount) {
return new Money(BigDecimal.valueOf(amount), "CNY");
}

// 业务行为:加法(返回新对象,保持不可变)
public Money add(Money other) {
assertSameCurrency(other);
return new Money(this.amount.add(other.amount), this.currency);
}

// 业务行为:减法
public Money subtract(Money other) {
assertSameCurrency(other);
return new Money(this.amount.subtract(other.amount), this.currency);
}

// 业务行为:乘法
public Money multiply(int multiplier) {
return new Money(this.amount.multiply(BigDecimal.valueOf(multiplier)), this.currency);
}

// 业务行为:折扣
public Money discount(Percentage percentage) {
BigDecimal discountAmount = this.amount.multiply(percentage.getValue());
return new Money(this.amount.subtract(discountAmount), this.currency);
}

// 业务行为:比较
public boolean isGreaterThan(Money other) {
assertSameCurrency(other);
return this.amount.compareTo(other.amount) > 0;
}

public boolean isLessThanOrEqualTo(Money other) {
assertSameCurrency(other);
return this.amount.compareTo(other.amount) <= 0;
}

private void assertSameCurrency(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("货币类型不同,无法计算");
}
}

// equals 和 hashCode 基于所有属性
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0 &&
Objects.equals(currency, money.currency);
}

@Override
public int hashCode() {
return Objects.hash(amount, currency);
}

@Override
public String toString() {
return currency + " " + amount;
}
}

// 百分比值对象
public class Percentage {
private final BigDecimal value;

private Percentage(BigDecimal value) {
if (value.compareTo(BigDecimal.ZERO) < 0 || value.compareTo(BigDecimal.ONE) > 1) {
throw new IllegalArgumentException("百分比必须在0-1之间");
}
this.value = value;
}

public static Percentage of(double value) {
return new Percentage(BigDecimal.valueOf(value));
}

public BigDecimal getValue() {
return value;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Percentage)) return false;
Percentage that = (Percentage) o;
return value.compareTo(that.value) == 0;
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}

// 地址值对象
public class Address {
private final String province;
private final String city;
private final String district;
private final String street;
private final String detail;
private final String zipCode;

private Address(String province, String city, String district,
String street, String detail, String zipCode) {
this.province = Objects.requireNonNull(province, "省份不能为空");
this.city = Objects.requireNonNull(city, "城市不能为空");
this.district = Objects.requireNonNull(district, "区县不能为空");
this.street = Objects.requireNonNull(street, "街道不能为空");
this.detail = Objects.requireNonNull(detail, "详细地址不能为空");
this.zipCode = zipCode;
}

public static Address of(String province, String city, String district,
String street, String detail, String zipCode) {
return new Address(province, city, district, street, detail, zipCode);
}

// 业务行为:获取完整地址
public String getFullAddress() {
return province + city + district + street + detail;
}

// 业务行为:判断是否同城
public boolean isSameCity(Address other) {
return this.province.equals(other.province) &&
this.city.equals(other.city);
}

// getter方法(无setter,保证不可变)
public String getProvince() { return province; }
public String getCity() { return city; }
// ... 其他getter

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Address)) return false;
Address address = (Address) o;
return Objects.equals(province, address.province) &&
Objects.equals(city, address.city) &&
Objects.equals(district, address.district) &&
Objects.equals(street, address.street) &&
Objects.equals(detail, address.detail) &&
Objects.equals(zipCode, address.zipCode);
}

@Override
public int hashCode() {
return Objects.hash(province, city, district, street, detail, zipCode);
}
}

实体 vs 值对象对比

维度 实体(Entity) 值对象(Value Object)
唯一标识 有ID 无ID
相等性 基于ID 基于所有属性
可变性 可变 不可变
生命周期 有独立生命周期 依附于实体
替换性 不可随意替换 可以替换
示例 Order, User, Product Money, Address, Email

3. 聚合(Aggregate)与聚合根(Aggregate Root)

**聚合(Aggregate)是一组相关对象的集合,作为一个整体来管理数据的一致性。聚合根(Aggregate Root)**是聚合的入口,外部只能通过聚合根访问聚合内的对象。

为什么需要聚合?

没有聚合的问题:

1
2
3
4
// 外部直接修改订单项,破坏了订单的一致性
OrderItem item = order.getItems().get(0);
item.setQuantity(1000); // 直接修改数量
// 订单的总金额没有更新!数据不一致!

使用聚合:

1
2
// 通过聚合根统一管理
order.updateItemQuantity(itemId, 1000); // 聚合根保证总金额同步更新

聚合的设计原则:

  1. 聚合根是唯一入口: 外部只能持有聚合根的引用
  2. 边界内保证一致性: 聚合内的所有对象一起保存、一起删除
  3. 聚合间只能引用ID: 不能直接持有其他聚合根的引用
  4. 聚合不宜过大: 通常只包含1-3个实体

代码示例:订单聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// 订单聚合根
public class Order {
// 聚合根ID
private OrderId id;

// 聚合内的实体和值对象
private UserId userId;
private List<OrderItem> items; // 订单项(聚合内实体)
private Money totalAmount; // 总金额(值对象)
private Address shippingAddress; // 收货地址(值对象)
private OrderStatus status;
private LocalDateTime createdAt;

// 私有构造函数
private Order() {
this.items = new ArrayList<>();
}

// 工厂方法:创建订单
public static Order create(UserId userId, List<OrderItem> items, Address shippingAddress) {
if (items == null || items.isEmpty()) {
throw new OrderException("订单至少包含一个商品");
}

Order order = new Order();
order.id = OrderId.generate();
order.userId = userId;
order.items = new ArrayList<>(items); // 防御性复制
order.shippingAddress = shippingAddress;
order.totalAmount = calculateTotalAmount(items);
order.status = OrderStatus.PENDING;
order.createdAt = LocalDateTime.now();

// 发布领域事件
DomainEventPublisher.publish(new OrderCreatedEvent(order.id));

return order;
}

// 业务行为:添加订单项(通过聚合根,保证一致性)
public void addItem(OrderItem newItem) {
if (this.status != OrderStatus.PENDING) {
throw new OrderException("只有待支付订单才能添加商品");
}

// 检查是否已存在相同商品
Optional<OrderItem> existingItem = items.stream()
.filter(item -> item.getProductId().equals(newItem.getProductId()))
.findFirst();

if (existingItem.isPresent()) {
// 合并数量
existingItem.get().increaseQuantity(newItem.getQuantity());
} else {
// 添加新商品
items.add(newItem);
}

// 重新计算总金额(保证一致性)
this.totalAmount = calculateTotalAmount(items);
}

// 业务行为:更新订单项数量(通过聚合根,保证一致性)
public void updateItemQuantity(OrderItemId itemId, int newQuantity) {
if (this.status != OrderStatus.PENDING) {
throw new OrderException("只有待支付订单才能修改商品数量");
}

OrderItem item = findItem(itemId);
item.updateQuantity(newQuantity);

// 重新计算总金额(保证一致性)
this.totalAmount = calculateTotalAmount(items);
}

// 业务行为:移除订单项(通过聚合根,保证一致性)
public void removeItem(OrderItemId itemId) {
if (this.status != OrderStatus.PENDING) {
throw new OrderException("只有待支付订单才能移除商品");
}

boolean removed = items.removeIf(item -> item.getId().equals(itemId));

if (!removed) {
throw new OrderException("订单项不存在");
}

if (items.isEmpty()) {
throw new OrderException("订单至少包含一个商品");
}

// 重新计算总金额(保证一致性)
this.totalAmount = calculateTotalAmount(items);
}

// 业务行为:支付订单
public void pay(Money paymentAmount) {
if (this.status != OrderStatus.PENDING) {
throw new OrderException("订单状态不允许支付");
}

if (!paymentAmount.equals(this.totalAmount)) {
throw new OrderException("支付金额与订单金额不符");
}

this.status = OrderStatus.PAID;

// 发布领域事件
DomainEventPublisher.publish(new OrderPaidEvent(this.id, paymentAmount));
}

// 业务行为:取消订单
public void cancel(String reason) {
if (this.status == OrderStatus.PAID) {
throw new OrderException("已支付订单不能取消,请申请退款");
}

if (this.status == OrderStatus.CANCELLED) {
throw new OrderException("订单已取消");
}

this.status = OrderStatus.CANCELLED;

// 发布领域事件
DomainEventPublisher.publish(new OrderCancelledEvent(this.id, reason));
}

// 私有辅助方法
private OrderItem findItem(OrderItemId itemId) {
return items.stream()
.filter(item -> item.getId().equals(itemId))
.findFirst()
.orElseThrow(() -> new OrderException("订单项不存在"));
}

private static Money calculateTotalAmount(List<OrderItem> items) {
return items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::add);
}

// 只提供必要的getter,不提供setter
public OrderId getId() { return id; }
public UserId getUserId() { return userId; }
public Money getTotalAmount() { return totalAmount; }
public OrderStatus getStatus() { return status; }

// 返回不可修改的列表,防止外部直接修改
public List<OrderItem> getItems() {
return Collections.unmodifiableList(items);
}
}

// 订单项实体(聚合内实体,不是聚合根)
public class OrderItem {
private OrderItemId id;
private ProductId productId; // 引用商品ID,不持有商品对象
private String productName; // 商品名称快照
private Money unitPrice; // 单价快照
private int quantity; // 数量
private Money subtotal; // 小计

// 包级可见的构造函数(只能被同一聚合内的对象创建)
OrderItem(ProductId productId, String productName, Money unitPrice, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("数量必须大于0");
}

this.id = OrderItemId.generate();
this.productId = productId;
this.productName = productName;
this.unitPrice = unitPrice;
this.quantity = quantity;
this.subtotal = unitPrice.multiply(quantity);
}

// 包级可见的方法(只能被聚合根调用)
void updateQuantity(int newQuantity) {
if (newQuantity <= 0) {
throw new IllegalArgumentException("数量必须大于0");
}
this.quantity = newQuantity;
this.subtotal = unitPrice.multiply(quantity);
}

void increaseQuantity(int increment) {
this.quantity += increment;
this.subtotal = unitPrice.multiply(quantity);
}

// getter方法
public OrderItemId getId() { return id; }
public ProductId getProductId() { return productId; }
public String getProductName() { return productName; }
public Money getUnitPrice() { return unitPrice; }
public int getQuantity() { return quantity; }
public Money getSubtotal() { return subtotal; }
}

聚合间的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ❌ 错误:聚合间不能直接持有引用
public class Order {
private User user; // 错误!不能直接持有User对象
}

// ✅ 正确:聚合间只能引用ID
public class Order {
private UserId userId; // 正确!只持有ID

// 如果需要用户信息,通过应用服务协调
}

// 应用服务协调多个聚合
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;

@Autowired
private UserRepository userRepository;

@Transactional
public OrderDTO getOrderDetail(OrderId orderId) {
// 加载订单聚合
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException());

// 加载用户聚合(如果需要)
User user = userRepository.findById(order.getUserId())
.orElseThrow(() -> new UserNotFoundException());

// 组装DTO返回
return OrderDTO.from(order, user);
}
}

聚合设计的关键问题:

  1. 聚合的大小

    • ✅ 小聚合(1-3个实体):性能好,易于维护
    • ❌ 大聚合(10+个实体):性能差,并发冲突多
  2. 聚合的边界

    • 遵循业务规则:一起变化的放在一起
    • 考虑事务边界:一个事务只修改一个聚合
    • 考虑并发性能:聚合越小,锁冲突越少
  3. 聚合间的一致性

    • 聚合内:强一致性(同一事务)
    • 聚合间:最终一致性(领域事件)

DDD分层架构

DDD通常采用四层架构,从上到下依次是:

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────┐
│ 用户界面层 (User Interface) │ ← 展示和交互
├─────────────────────────────────────┤
│ 应用层 (Application Layer) │ ← 编排和协调
├─────────────────────────────────────┤
│ 领域层 (Domain Layer) │ ← 核心业务逻辑
├─────────────────────────────────────┤
│ 基础设施层 (Infrastructure Layer) │ ← 技术实现
└─────────────────────────────────────┘

1. 用户界面层(User Interface Layer)

职责:

  • 接收用户请求
  • 展示数据给用户
  • 调用应用层服务

包含:

  • Controller
  • DTO(Data Transfer Object)
  • 视图模板

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// REST控制器
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderApplicationService orderApplicationService;

// 创建订单
@PostMapping
public Response<OrderDTO> createOrder(@RequestBody CreateOrderRequest request) {
OrderDTO order = orderApplicationService.createOrder(request);
return Response.success(order);
}

// 支付订单
@PostMapping("/{orderId}/pay")
public Response<Void> payOrder(
@PathVariable String orderId,
@RequestBody PaymentRequest request
) {
orderApplicationService.payOrder(OrderId.of(orderId), request);
return Response.success();
}

// 查询订单
@GetMapping("/{orderId}")
public Response<OrderDTO> getOrder(@PathVariable String orderId) {
OrderDTO order = orderApplicationService.getOrder(OrderId.of(orderId));
return Response.success(order);
}
}

// DTO:用于前后端数据传输
public class OrderDTO {
private String orderId;
private String userId;
private List<OrderItemDTO> items;
private BigDecimal totalAmount;
private String status;
private String shippingAddress;
private LocalDateTime createdAt;

// 从领域对象转换为DTO
public static OrderDTO from(Order order) {
OrderDTO dto = new OrderDTO();
dto.orderId = order.getId().getValue();
dto.userId = order.getUserId().getValue();
dto.items = order.getItems().stream()
.map(OrderItemDTO::from)
.collect(Collectors.toList());
dto.totalAmount = order.getTotalAmount().getAmount();
dto.status = order.getStatus().name();
dto.shippingAddress = order.getShippingAddress().getFullAddress();
dto.createdAt = order.getCreatedAt();
return dto;
}
}

2. 应用层(Application Layer)

职责:

  • 编排领域对象完成业务流程
  • 协调多个聚合
  • 管理事务
  • 发布和订阅领域事件
  • 权限校验

特点:

  • 薄薄的一层: 不包含业务逻辑,只负责协调
  • 无状态: 不保存业务状态
  • 事务边界: 一个应用服务方法 = 一个事务

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 订单应用服务
@Service
@Transactional
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;

@Autowired
private ProductRepository productRepository;

@Autowired
private UserRepository userRepository;

@Autowired
private InventoryService inventoryService;

@Autowired
private DomainEventPublisher eventPublisher;

/**
* 创建订单
* 应用服务的职责:
* 1. 参数校验和转换
* 2. 加载必要的领域对象
* 3. 调用领域对象的业务方法
* 4. 保存聚合
* 5. 发布领域事件
*/
public OrderDTO createOrder(CreateOrderRequest request) {
// 1. 参数校验
if (request.getItems() == null || request.getItems().isEmpty()) {
throw new IllegalArgumentException("订单至少包含一个商品");
}

// 2. 加载用户聚合
UserId userId = UserId.of(request.getUserId());
User user = userRepository.findById(userId)
.orElseThrow(() -> new UserNotFoundException());

// 3. 加载商品信息,构建订单项
List<OrderItem> orderItems = new ArrayList<>();
for (CreateOrderItemRequest itemReq : request.getItems()) {
ProductId productId = ProductId.of(itemReq.getProductId());
Product product = productRepository.findById(productId)
.orElseThrow(() -> new ProductNotFoundException());

// 检查库存
if (!inventoryService.checkStock(productId, itemReq.getQuantity())) {
throw new InsufficientStockException("商品库存不足");
}

// 构建订单项(使用商品的当前价格)
OrderItem orderItem = new OrderItem(
productId,
product.getName(),
product.getCurrentPrice(),
itemReq.getQuantity()
);
orderItems.add(orderItem);
}

// 4. 构建地址值对象
Address shippingAddress = Address.of(
request.getProvince(),
request.getCity(),
request.getDistrict(),
request.getStreet(),
request.getDetail(),
request.getZipCode()
);

// 5. 调用领域对象创建订单(业务逻辑在领域层)
Order order = Order.create(userId, orderItems, shippingAddress);

// 6. 应用会员折扣(如果是VIP)
if (user.isVip()) {
DiscountPolicy vipDiscount = new VipDiscountPolicy();
order.applyDiscount(vipDiscount);
}

// 7. 保存订单聚合
orderRepository.save(order);

// 8. 扣减库存(通过领域服务或应用服务协调)
for (OrderItem item : orderItems) {
inventoryService.deductStock(item.getProductId(), item.getQuantity());
}

// 9. 发布领域事件(异步处理通知等)
eventPublisher.publish(new OrderCreatedEvent(order.getId()));

// 10. 返回DTO
return OrderDTO.from(order);
}

/**
* 支付订单
*/
public void payOrder(OrderId orderId, PaymentRequest request) {
// 1. 加载订单聚合
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException());

// 2. 调用领域对象的支付方法(业务逻辑在领域层)
Money paymentAmount = Money.yuan(request.getAmount());
order.pay(paymentAmount);

// 3. 保存订单聚合
orderRepository.save(order);

// 4. 发布领域事件(触发后续流程:发货、积分等)
eventPublisher.publish(new OrderPaidEvent(orderId, paymentAmount));
}

/**
* 取消订单
*/
public void cancelOrder(OrderId orderId, String reason) {
// 1. 加载订单聚合
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException());

// 2. 调用领域对象的取消方法(业务逻辑在领域层)
order.cancel(reason);

// 3. 保存订单聚合
orderRepository.save(order);

// 4. 恢复库存
for (OrderItem item : order.getItems()) {
inventoryService.restoreStock(item.getProductId(), item.getQuantity());
}

// 5. 发布领域事件
eventPublisher.publish(new OrderCancelledEvent(orderId, reason));
}

/**
* 查询订单(只读操作,不需要加载完整聚合)
*/
@Transactional(readOnly = true)
public OrderDTO getOrder(OrderId orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException());
return OrderDTO.from(order);
}
}

应用服务 vs 领域服务:

维度 应用服务 领域服务
职责 编排协调 业务逻辑
状态 无状态 可以有状态
事务 管理事务 不关心事务
依赖 依赖多个聚合 依赖领域对象
示例 OrderApplicationService PricingService

3. 领域层(Domain Layer)

职责:

  • 实现核心业务逻辑
  • 定义领域模型
  • 包含业务规则和约束

包含:

  • 实体(Entity)
  • 值对象(Value Object)
  • 聚合(Aggregate)
  • 领域服务(Domain Service)
  • 领域事件(Domain Event)
  • 仓储接口(Repository Interface)
  • 工厂(Factory)

示例:前面已经展示了Order、OrderItem、Money等领域对象

4. 基础设施层(Infrastructure Layer)

职责:

  • 为其他层提供技术支撑
  • 实现仓储接口
  • 实现消息发布
  • 实现配置管理

包含:

  • 仓储实现(Repository Implementation)
  • 消息发送
  • 缓存
  • 日志
  • 配置

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// 仓储实现
@Repository
public class OrderRepositoryImpl implements OrderRepository {
@Autowired
private JpaOrderRepository jpaRepository; // Spring Data JPA

@Autowired
private OrderMapper orderMapper; // 领域对象与数据模型的转换器

@Override
public Optional<Order> findById(OrderId orderId) {
return jpaRepository.findById(orderId.getValue())
.map(orderMapper::toDomain); // 数据模型 → 领域对象
}

@Override
public void save(Order order) {
OrderPO po = orderMapper.toPO(order); // 领域对象 → 数据模型
jpaRepository.save(po);
}

@Override
public void delete(Order order) {
jpaRepository.deleteById(order.getId().getValue());
}
}

// JPA Repository
public interface JpaOrderRepository extends JpaRepository<OrderPO, String> {
}

// 数据模型(持久化对象)
@Entity
@Table(name = "orders")
public class OrderPO {
@Id
private String id;

@Column(name = "user_id")
private String userId;

@Column(name = "total_amount")
private BigDecimal totalAmount;

@Column(name = "status")
private String status;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "order_id")
private List<OrderItemPO> items;

// 地址字段
private String province;
private String city;
private String district;
private String street;
private String detail;
private String zipCode;

@Column(name = "created_at")
private LocalDateTime createdAt;

// getter/setter
}

@Entity
@Table(name = "order_items")
public class OrderItemPO {
@Id
private String id;

@Column(name = "order_id")
private String orderId;

@Column(name = "product_id")
private String productId;

@Column(name = "product_name")
private String productName;

@Column(name = "unit_price")
private BigDecimal unitPrice;

@Column(name = "quantity")
private Integer quantity;

@Column(name = "subtotal")
private BigDecimal subtotal;

// getter/setter
}

// 领域对象与数据模型的转换器
@Component
public class OrderMapper {
// 数据模型 → 领域对象
public Order toDomain(OrderPO po) {
// 重建订单项
List<OrderItem> items = po.getItems().stream()
.map(this::toDomainItem)
.collect(Collectors.toList());

// 重建地址
Address address = Address.of(
po.getProvince(),
po.getCity(),
po.getDistrict(),
po.getStreet(),
po.getDetail(),
po.getZipCode()
);

// 使用反射或工厂方法重建Order对象
// (这里简化处理,实际需要更复杂的重建逻辑)
return Order.reconstruct(
OrderId.of(po.getId()),
UserId.of(po.getUserId()),
items,
Money.of(po.getTotalAmount(), "CNY"),
address,
OrderStatus.valueOf(po.getStatus()),
po.getCreatedAt()
);
}

private OrderItem toDomainItem(OrderItemPO po) {
return OrderItem.reconstruct(
OrderItemId.of(po.getId()),
ProductId.of(po.getProductId()),
po.getProductName(),
Money.of(po.getUnitPrice(), "CNY"),
po.getQuantity()
);
}

// 领域对象 → 数据模型
public OrderPO toPO(Order order) {
OrderPO po = new OrderPO();
po.setId(order.getId().getValue());
po.setUserId(order.getUserId().getValue());
po.setTotalAmount(order.getTotalAmount().getAmount());
po.setStatus(order.getStatus().name());
po.setProvince(order.getShippingAddress().getProvince());
po.setCity(order.getShippingAddress().getCity());
po.setDistrict(order.getShippingAddress().getDistrict());
po.setStreet(order.getShippingAddress().getStreet());
po.setDetail(order.getShippingAddress().getDetail());
po.setZipCode(order.getShippingAddress().getZipCode());
po.setCreatedAt(order.getCreatedAt());

List<OrderItemPO> itemPOs = order.getItems().stream()
.map(this::toPOItem)
.collect(Collectors.toList());
po.setItems(itemPOs);

return po;
}

private OrderItemPO toPOItem(OrderItem item) {
OrderItemPO po = new OrderItemPO();
po.setId(item.getId().getValue());
po.setProductId(item.getProductId().getValue());
po.setProductName(item.getProductName());
po.setUnitPrice(item.getUnitPrice().getAmount());
po.setQuantity(item.getQuantity());
po.setSubtotal(item.getSubtotal().getAmount());
return po;
}
}

分层架构的依赖关系:

1
2
3
用户界面层  ──→  应用层  ──→  领域层
↓ ↓ ↑
└───────→ 基础设施层 ──┘

关键原则:

  1. 依赖倒置: 基础设施层实现领域层定义的接口
  2. 领域层独立: 领域层不依赖任何其他层
  3. 应用层协调: 应用层编排领域对象和基础设施

4. 领域服务(Domain Service)

**领域服务(Domain Service)**用于封装不属于任何实体或值对象的业务逻辑,通常涉及多个聚合或值对象的协作。

何时使用领域服务?

当遇到以下情况时,考虑使用领域服务:

  1. 业务逻辑涉及多个聚合
  2. 业务逻辑不自然属于任何一个实体
  3. 无状态的业务操作

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 定价领域服务
public interface PricingService {
Money calculateOrderPrice(Order order);
}

@Service
public class PricingServiceImpl implements PricingService {
@Override
public Money calculateOrderPrice(Order order) {
Money basePrice = order.getTotalAmount();

// 根据订单金额计算折扣
Money discountedPrice = basePrice;
if (basePrice.isGreaterThan(Money.yuan(1000))) {
// 满1000减100
discountedPrice = basePrice.subtract(Money.yuan(100));
} else if (basePrice.isGreaterThan(Money.yuan(500))) {
// 满500减50
discountedPrice = basePrice.subtract(Money.yuan(50));
}

return discountedPrice;
}
}

// 转账领域服务(涉及两个聚合)
public interface TransferService {
void transfer(AccountId fromAccountId, AccountId toAccountId, Money amount);
}

@Service
public class TransferServiceImpl implements TransferService {
@Autowired
private AccountRepository accountRepository;

@Override
@Transactional
public void transfer(AccountId fromAccountId, AccountId toAccountId, Money amount) {
// 加载两个账户聚合
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new AccountNotFoundException());

Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new AccountNotFoundException());

// 调用领域对象的方法
fromAccount.withdraw(amount); // 扣款
toAccount.deposit(amount); // 入账

// 保存两个聚合
accountRepository.save(fromAccount);
accountRepository.save(toAccount);

// 发布领域事件
DomainEventPublisher.publish(new TransferCompletedEvent(fromAccountId, toAccountId, amount));
}
}

领域服务 vs 应用服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 领域服务:包含业务逻辑
@Service
public class PricingService {
public Money calculateDiscount(Order order, User user) {
// 业务规则:VIP用户打9折,普通用户打95折
if (user.isVip()) {
return order.getTotalAmount().multiply(0.1);
} else {
return order.getTotalAmount().multiply(0.05);
}
}
}

// 应用服务:协调领域对象和领域服务
@Service
@Transactional
public class OrderApplicationService {
@Autowired
private PricingService pricingService; // 领域服务

public OrderDTO createOrder(CreateOrderRequest request) {
// 1. 加载领域对象
Order order = Order.create(...);
User user = userRepository.findById(...);

// 2. 调用领域服务计算折扣
Money discount = pricingService.calculateDiscount(order, user);

// 3. 应用折扣
order.applyDiscount(discount);

// 4. 保存
orderRepository.save(order);

return OrderDTO.from(order);
}
}

5. 领域事件(Domain Event)

**领域事件(Domain Event)**表示领域中发生的重要业务事件,用于实现聚合间的最终一致性和解耦。

为什么需要领域事件?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 不使用领域事件:紧耦合
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;

@Autowired
private NotificationService notificationService;

@Autowired
private PointsService pointsService;

public void payOrder(OrderId orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
order.pay();

// 直接调用其他服务:紧耦合
inventoryService.reserveStock(order);
notificationService.sendPaymentNotification(order);
pointsService.addPoints(order);

orderRepository.save(order);
}
}

// 使用领域事件:解耦
@Service
public class OrderService {
public void payOrder(OrderId orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
order.pay(); // 内部会发布 OrderPaidEvent
orderRepository.save(order);

// 其他操作通过事件监听器异步处理,解耦
}
}

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// 1. 定义领域事件
public abstract class DomainEvent {
private final String eventId;
private final LocalDateTime occurredOn;

protected DomainEvent() {
this.eventId = UUID.randomUUID().toString();
this.occurredOn = LocalDateTime.now();
}

public String getEventId() { return eventId; }
public LocalDateTime getOccurredOn() { return occurredOn; }
}

// 订单已创建事件
public class OrderCreatedEvent extends DomainEvent {
private final OrderId orderId;

public OrderCreatedEvent(OrderId orderId) {
super();
this.orderId = orderId;
}

public OrderId getOrderId() { return orderId; }
}

// 订单已支付事件
public class OrderPaidEvent extends DomainEvent {
private final OrderId orderId;
private final Money paymentAmount;

public OrderPaidEvent(OrderId orderId, Money paymentAmount) {
super();
this.orderId = orderId;
this.paymentAmount = paymentAmount;
}

public OrderId getOrderId() { return orderId; }
public Money getPaymentAmount() { return paymentAmount; }
}

// 2. 领域事件发布器
@Component
public class DomainEventPublisher {
private static ApplicationEventPublisher publisher;

@Autowired
public void setPublisher(ApplicationEventPublisher publisher) {
DomainEventPublisher.publisher = publisher;
}

public static void publish(DomainEvent event) {
if (publisher != null) {
publisher.publishEvent(event);
}
}
}

// 3. 在聚合中发布事件
public class Order {
public void pay(Money paymentAmount) {
// 业务逻辑
this.status = OrderStatus.PAID;

// 发布领域事件
DomainEventPublisher.publish(new OrderPaidEvent(this.id, paymentAmount));
}
}

// 4. 事件监听器
@Component
public class OrderEventListener {
@Autowired
private InventoryService inventoryService;

@Autowired
private NotificationService notificationService;

@Autowired
private PointsService pointsService;

// 监听订单已支付事件
@EventListener
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handleOrderPaid(OrderPaidEvent event) {
OrderId orderId = event.getOrderId();

try {
// 扣减库存
inventoryService.deductStock(orderId);

// 发送支付成功通知
notificationService.sendPaymentSuccessNotification(orderId);

// 增加积分
pointsService.addPoints(orderId, event.getPaymentAmount());

} catch (Exception e) {
log.error("处理订单支付事件失败: orderId={}", orderId, e);
// 可以发送到死信队列或重试
}
}

// 监听订单已取消事件
@EventListener
public void handleOrderCancelled(OrderCancelledEvent event) {
// 恢复库存
inventoryService.restoreStock(event.getOrderId());

// 发送取消通知
notificationService.sendCancellationNotification(event.getOrderId());
}
}

领域事件的优势:

  1. 解耦: 聚合之间通过事件通信,而非直接调用
  2. 可扩展: 新增事件监听器不影响原有代码
  3. 最终一致性: 支持异步处理,提高性能
  4. 审计追踪: 事件记录了业务发生的历史

6. 仓储(Repository)

**仓储(Repository)**提供了聚合的持久化和查询接口,封装了数据访问的细节。

仓储的特点:

  1. 只为聚合根提供仓储
  2. 仓储接口定义在领域层
  3. 仓储实现在基础设施层
  4. 仓储使用领域对象,而非数据模型

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 1. 仓储接口(定义在领域层)
public interface OrderRepository {
// 通过ID查找聚合
Optional<Order> findById(OrderId orderId);

// 保存聚合
void save(Order order);

// 删除聚合
void delete(Order order);

// 领域查询方法
List<Order> findByUserId(UserId userId);
List<Order> findByStatus(OrderStatus status);
Optional<Order> findByIdWithItems(OrderId orderId);
}

// 2. 仓储实现(实现在基础设施层)
@Repository
public class OrderRepositoryImpl implements OrderRepository {
@Autowired
private JpaOrderRepository jpaRepository;

@Autowired
private OrderMapper orderMapper;

@Override
public Optional<Order> findById(OrderId orderId) {
return jpaRepository.findById(orderId.getValue())
.map(orderMapper::toDomain);
}

@Override
public void save(Order order) {
OrderPO po = orderMapper.toPO(order);
jpaRepository.save(po);
}

@Override
public void delete(Order order) {
jpaRepository.deleteById(order.getId().getValue());
}

@Override
public List<Order> findByUserId(UserId userId) {
List<OrderPO> pos = jpaRepository.findByUserId(userId.getValue());
return pos.stream()
.map(orderMapper::toDomain)
.collect(Collectors.toList());
}

@Override
public List<Order> findByStatus(OrderStatus status) {
List<OrderPO> pos = jpaRepository.findByStatus(status.name());
return pos.stream()
.map(orderMapper::toDomain)
.collect(Collectors.toList());
}

@Override
public Optional<Order> findByIdWithItems(OrderId orderId) {
// 使用JOIN FETCH优化查询
return jpaRepository.findByIdWithItems(orderId.getValue())
.map(orderMapper::toDomain);
}
}

// 3. Spring Data JPA接口
public interface JpaOrderRepository extends JpaRepository<OrderPO, String> {
List<OrderPO> findByUserId(String userId);
List<OrderPO> findByStatus(String status);

@Query("SELECT o FROM OrderPO o LEFT JOIN FETCH o.items WHERE o.id = :orderId")
Optional<OrderPO> findByIdWithItems(@Param("orderId") String orderId);
}

仓储的最佳实践:

  1. 只为聚合根创建仓储,不为聚合内的实体创建仓储
  2. 仓储方法返回领域对象,而非数据模型
  3. 复杂查询可以使用CQRS模式,单独处理

7. 工厂(Factory)

**工厂(Factory)**用于创建复杂的聚合或实体,封装创建逻辑。

何时使用工厂?

  • 创建逻辑复杂
  • 需要校验多个参数
  • 创建过程涉及多个步骤

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 简单情况:使用静态工厂方法
public class Order {
// 静态工厂方法
public static Order create(UserId userId, List<OrderItem> items, Address shippingAddress) {
// 创建逻辑
Order order = new Order();
order.id = OrderId.generate();
order.userId = userId;
order.items = items;
order.shippingAddress = shippingAddress;
order.totalAmount = calculateTotalAmount(items);
order.status = OrderStatus.PENDING;
return order;
}
}

// 复杂情况:使用独立的工厂类
@Component
public class OrderFactory {
@Autowired
private ProductRepository productRepository;

@Autowired
private PricingService pricingService;

/**
* 从购物车创建订单
*/
public Order createFromCart(UserId userId, ShoppingCart cart, Address shippingAddress) {
// 1. 加载商品信息
List<OrderItem> orderItems = new ArrayList<>();
for (CartItem cartItem : cart.getItems()) {
Product product = productRepository.findById(cartItem.getProductId())
.orElseThrow(() -> new ProductNotFoundException());

// 使用当前价格创建订单项
OrderItem orderItem = new OrderItem(
product.getId(),
product.getName(),
product.getCurrentPrice(),
cartItem.getQuantity()
);
orderItems.add(orderItem);
}

// 2. 创建订单
Order order = Order.create(userId, orderItems, shippingAddress);

// 3. 应用定价策略
Money basePrice = order.getTotalAmount();
Money finalPrice = pricingService.calculateFinalPrice(basePrice, userId);
if (!finalPrice.equals(basePrice)) {
Money discount = basePrice.subtract(finalPrice);
order.applyDiscount(new FixedAmountDiscount(discount));
}

return order;
}

/**
* 重建订单(从数据库)
*/
public Order reconstruct(OrderId orderId, UserId userId, List<OrderItem> items,
Money totalAmount, Address shippingAddress,
OrderStatus status, LocalDateTime createdAt) {
// 使用反射或特殊构造方法重建对象
Order order = new Order();
order.setId(orderId);
order.setUserId(userId);
order.setItems(items);
order.setTotalAmount(totalAmount);
order.setShippingAddress(shippingAddress);
order.setStatus(status);
order.setCreatedAt(createdAt);
return order;
}
}

DDD进阶模式

CQRS(命令查询职责分离)

**CQRS(Command Query Responsibility Segregation)**将读操作(查询)和写操作(命令)分离,使用不同的模型处理。

为什么需要CQRS?

传统方式的问题:

1
2
3
4
5
6
7
8
9
// 同一个领域模型既用于写又用于读
Order order = orderRepository.findById(orderId); // 查询
order.pay(paymentAmount); // 修改
orderRepository.save(order); // 保存

// 问题:
// 1. 查询需要的字段和修改需要的字段不一致
// 2. 查询可能需要跨多个聚合,但修改只涉及一个聚合
// 3. 查询性能要求高,但领域模型查询慢

CQRS方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// 命令侧(写):使用领域模型
@Service
@Transactional
public class OrderCommandService {
@Autowired
private OrderRepository orderRepository; // 领域仓储

// 命令:修改订单
public void payOrder(PayOrderCommand command) {
Order order = orderRepository.findById(command.getOrderId())
.orElseThrow(() -> new OrderNotFoundException());

order.pay(command.getPaymentAmount());
orderRepository.save(order);

// 发布事件,同步到查询侧
DomainEventPublisher.publish(new OrderPaidEvent(order.getId(), command.getPaymentAmount()));
}
}

// 查询侧(读):使用专门的查询模型
@Service
public class OrderQueryService {
@Autowired
private JdbcTemplate jdbcTemplate; // 直接查询数据库

// 查询:订单列表(优化的DTO)
public Page<OrderListDTO> queryOrderList(OrderQueryCriteria criteria) {
// 使用优化的SQL,直接返回DTO
String sql = """
SELECT o.id, o.user_id, o.total_amount, o.status, o.created_at,
u.username, u.avatar
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE o.user_id = ?
ORDER BY o.created_at DESC
LIMIT ? OFFSET ?
""";

List<OrderListDTO> orders = jdbcTemplate.query(sql,
new Object[]{criteria.getUserId(), criteria.getPageSize(), criteria.getOffset()},
(rs, rowNum) -> {
OrderListDTO dto = new OrderListDTO();
dto.setOrderId(rs.getString("id"));
dto.setTotalAmount(rs.getBigDecimal("total_amount"));
dto.setStatus(rs.getString("status"));
dto.setCreatedAt(rs.getTimestamp("created_at").toLocalDateTime());
dto.setUsername(rs.getString("username"));
dto.setUserAvatar(rs.getString("avatar"));
return dto;
}
);

return new Page<>(orders, criteria.getPageNumber(), criteria.getPageSize());
}

// 查询:订单详情(跨多个表)
public OrderDetailDTO queryOrderDetail(String orderId) {
// 复杂查询,可能跨多个聚合
String sql = """
SELECT o.*, u.username, u.phone,
p.name as product_name, p.image_url,
oi.quantity, oi.unit_price, oi.subtotal
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN order_items oi ON o.id = oi.order_id
LEFT JOIN products p ON oi.product_id = p.id
WHERE o.id = ?
""";

// 组装复杂的DTO
// ...
}
}

// 查询模型的数据表(可选:使用物化视图或单独的查询表)
CREATE TABLE order_query_view AS
SELECT
o.id as order_id,
o.user_id,
o.total_amount,
o.status,
o.created_at,
u.username,
u.phone,
COUNT(oi.id) as item_count
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN order_items oi ON o.id = oi.order_id
GROUP BY o.id;

// 通过事件监听器同步查询模型
@Component
public class OrderQueryModelUpdater {
@Autowired
private JdbcTemplate jdbcTemplate;

@EventListener
@Async
public void handleOrderPaid(OrderPaidEvent event) {
// 更新查询模型
jdbcTemplate.update(
"UPDATE order_query_view SET status = ? WHERE order_id = ?",
"PAID", event.getOrderId().getValue()
);
}
}

CQRS的优势:

  1. 性能优化: 读写分离,各自优化
  2. 灵活查询: 查询侧可以跨多个聚合
  3. 可扩展性: 读写可以独立扩展
  4. 简化设计: 领域模型专注于业务逻辑

Event Sourcing(事件溯源)

Event Sourcing不存储对象的当前状态,而是存储导致状态变化的所有事件,通过重放事件来重建对象状态。

传统方式 vs Event Sourcing:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 传统方式:存储当前状态
// orders表
| id | user_id | total_amount | status | created_at |
|----|---------|--------------|--------|------------|
| 1 | user1 | 100.00 | PAID | 2025-01-13 |

// Event Sourcing:存储事件流
// order_events表
| event_id | aggregate_id | event_type | event_data | occurred_at |
|----------|--------------|-------------------|-------------------------------|-------------|
| 1 | order1 | OrderCreated | {"userId":"user1","amount":100}| 2025-01-13 |
| 2 | order1 | OrderPaid | {"amount":100} | 2025-01-13 |
| 3 | order1 | OrderShipped | {"trackingNumber":"123"} | 2025-01-14 |

简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 1. 事件存储
@Entity
@Table(name = "domain_events")
public class StoredEvent {
@Id
@GeneratedValue
private Long id;

private String aggregateId;
private String eventType;

@Column(columnDefinition = "TEXT")
private String eventData; // JSON格式

private LocalDateTime occurredAt;

@Version
private Long version; // 乐观锁
}

// 2. 事件存储仓储
public interface EventStore {
void saveEvent(String aggregateId, DomainEvent event);
List<DomainEvent> loadEvents(String aggregateId);
}

@Repository
public class EventStoreImpl implements EventStore {
@Autowired
private JpaEventRepository jpaRepository;

@Autowired
private ObjectMapper objectMapper;

@Override
public void saveEvent(String aggregateId, DomainEvent event) {
StoredEvent storedEvent = new StoredEvent();
storedEvent.setAggregateId(aggregateId);
storedEvent.setEventType(event.getClass().getSimpleName());
storedEvent.setEventData(objectMapper.writeValueAsString(event));
storedEvent.setOccurredAt(event.getOccurredOn());

jpaRepository.save(storedEvent);
}

@Override
public List<DomainEvent> loadEvents(String aggregateId) {
List<StoredEvent> storedEvents = jpaRepository.findByAggregateIdOrderByOccurredAt(aggregateId);

return storedEvents.stream()
.map(this::deserializeEvent)
.collect(Collectors.toList());
}

private DomainEvent deserializeEvent(StoredEvent storedEvent) {
try {
Class<? extends DomainEvent> eventClass =
(Class<? extends DomainEvent>) Class.forName("com.example.domain.event." + storedEvent.getEventType());
return objectMapper.readValue(storedEvent.getEventData(), eventClass);
} catch (Exception e) {
throw new RuntimeException("反序列化事件失败", e);
}
}
}

// 3. 支持事件溯源的聚合
public class Order {
private OrderId id;
private List<DomainEvent> uncommittedEvents = new ArrayList<>();

// 从事件重建聚合
public static Order loadFromHistory(List<DomainEvent> history) {
Order order = new Order();
for (DomainEvent event : history) {
order.apply(event);
}
return order;
}

// 应用事件(修改状态)
private void apply(DomainEvent event) {
if (event instanceof OrderCreatedEvent) {
OrderCreatedEvent e = (OrderCreatedEvent) event;
this.id = e.getOrderId();
this.status = OrderStatus.PENDING;
} else if (event instanceof OrderPaidEvent) {
OrderPaidEvent e = (OrderPaidEvent) event;
this.status = OrderStatus.PAID;
} else if (event instanceof OrderShippedEvent) {
OrderShippedEvent e = (OrderShippedEvent) event;
this.status = OrderStatus.SHIPPED;
}
}

// 业务方法:产生新事件
public void pay(Money paymentAmount) {
// 业务规则验证
if (this.status != OrderStatus.PENDING) {
throw new OrderException("订单状态不允许支付");
}

// 产生事件
OrderPaidEvent event = new OrderPaidEvent(this.id, paymentAmount);

// 应用事件(修改状态)
apply(event);

// 记录未提交的事件
uncommittedEvents.add(event);
}

public List<DomainEvent> getUncommittedEvents() {
return new ArrayList<>(uncommittedEvents);
}

public void markEventsAsCommitted() {
uncommittedEvents.clear();
}
}

// 4. 仓储实现
@Repository
public class EventSourcedOrderRepository implements OrderRepository {
@Autowired
private EventStore eventStore;

@Override
public Optional<Order> findById(OrderId orderId) {
// 从事件存储加载事件
List<DomainEvent> events = eventStore.loadEvents(orderId.getValue());

if (events.isEmpty()) {
return Optional.empty();
}

// 从事件重建聚合
Order order = Order.loadFromHistory(events);
return Optional.of(order);
}

@Override
public void save(Order order) {
// 保存未提交的事件
for (DomainEvent event : order.getUncommittedEvents()) {
eventStore.saveEvent(order.getId().getValue(), event);
}

// 标记事件已提交
order.markEventsAsCommitted();
}
}

Event Sourcing的优势:

  1. 完整的审计日志: 所有状态变化都有记录
  2. 时间旅行: 可以重建任意时刻的状态
  3. 事件重放: 可以用于调试和数据修复
  4. 自然支持CQRS: 事件可以投影到多个查询模型

Event Sourcing的挑战:

  1. 查询复杂: 需要重放事件或维护查询模型
  2. 事件版本管理: 事件结构变化需要版本控制
  3. 存储成本: 存储所有事件,数据量大
  4. 学习成本: 思维模式转变

DDD最佳实践与常见误区

最佳实践

1. 如何识别聚合边界?

原则:

  • 一起变化的放在一起
  • 考虑事务边界:一个事务只修改一个聚合
  • 考虑并发性能:聚合越小,锁冲突越少

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
❌ 错误:聚合过大
Order (聚合根)
├── OrderItem
├── Payment
├── Invoice
├── Shipment
└── Review

问题:
- 订单支付和订单评价不需要在同一事务
- 并发修改冲突多

✅ 正确:拆分为多个聚合
Order (聚合根) Payment (聚合根)
├── OrderItem
Invoice (聚合根)

Shipment (聚合根) Review (聚合根)

通过领域事件保持一致性:
Order.pay() → OrderPaidEvent → ShipmentService.createShipment()

2. 如何设计值对象?

原则:

  • 优先使用值对象,而非基本类型
  • 值对象必须不可变
  • 值对象封装相关的业务逻辑

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// ❌ 错误:使用基本类型
public class Order {
private BigDecimal amount; // 金额?数量?不明确
private String email; // 没有格式验证
}

// ✅ 正确:使用值对象
public class Order {
private Money amount; // 明确是金额,包含货币单位
private Email email; // 格式验证在值对象内部
}

// Email值对象
public class Email {
private final String value;

private Email(String value) {
// 格式验证
if (!value.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
throw new IllegalArgumentException("邮箱格式不正确");
}
this.value = value;
}

public static Email of(String value) {
return new Email(value);
}

public String getValue() { return value; }

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Email)) return false;
Email email = (Email) o;
return Objects.equals(value, email.value);
}

@Override
public int hashCode() {
return Objects.hash(value);
}
}

3. 贫血模型 vs 充血模型

贫血模型(Anti-Pattern):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 贫血模型:只有数据,没有行为
public class Order {
private Long id;
private BigDecimal amount;
private String status;

// 只有getter/setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ...
}

// 业务逻辑在Service层
@Service
public class OrderService {
public void payOrder(Order order, BigDecimal paymentAmount) {
// 验证
if (!order.getAmount().equals(paymentAmount)) {
throw new Exception("金额不匹配");
}

// 修改状态
order.setStatus("PAID");

// 保存
orderRepository.save(order);
}
}

充血模型(DDD推荐):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 充血模型:数据+行为
public class Order {
private OrderId id;
private Money amount;
private OrderStatus status;

// 业务行为
public void pay(Money paymentAmount) {
// 业务规则验证
if (!paymentAmount.equals(this.amount)) {
throw new OrderException("支付金额与订单金额不符");
}

if (this.status != OrderStatus.PENDING) {
throw new OrderException("订单状态不允许支付");
}

// 修改状态
this.status = OrderStatus.PAID;

// 发布事件
DomainEventPublisher.publish(new OrderPaidEvent(this.id, paymentAmount));
}

// 只提供必要的getter,不提供setter
public OrderId getId() { return id; }
public Money getAmount() { return amount; }
public OrderStatus getStatus() { return status; }
}

4. DDD与微服务的结合

原则: 一个限界上下文 = 一个微服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
电商系统的微服务划分:

订单微服务 (Order Service)
├── 订单上下文
│ ├── Order (聚合根)
│ └── OrderItem

商品微服务 (Product Service)
├── 商品上下文
│ ├── Product (聚合根)
│ └── SKU

库存微服务 (Inventory Service)
├── 库存上下文
│ ├── Stock (聚合根)
│ └── Warehouse

支付微服务 (Payment Service)
├── 支付上下文
│ └── Payment (聚合根)

会员微服务 (Member Service)
├── 会员上下文
│ ├── Member (聚合根)
│ └── MemberLevel

服务间通信:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 通过HTTP API调用
@Service
public class OrderApplicationService {
@Autowired
private RestTemplate restTemplate;

public void createOrder(CreateOrderRequest request) {
// 调用商品服务获取商品信息
ProductDTO product = restTemplate.getForObject(
"http://product-service/api/products/" + request.getProductId(),
ProductDTO.class
);

// 创建订单
Order order = Order.create(...);
orderRepository.save(order);

// 发布事件,异步处理
eventPublisher.publish(new OrderCreatedEvent(order.getId()));
}
}

// 通过消息队列异步通信
@Component
public class OrderEventPublisher {
@Autowired
private RabbitTemplate rabbitTemplate;

public void publishOrderCreated(Order order) {
OrderCreatedMessage message = new OrderCreatedMessage();
message.setOrderId(order.getId().getValue());
message.setUserId(order.getUserId().getValue());
message.setAmount(order.getTotalAmount().getAmount());

rabbitTemplate.convertAndSend("order.exchange", "order.created", message);
}
}

常见误区

1. 过度设计

误区: 为简单的CRUD系统使用DDD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ❌ 不必要的复杂度
// 一个简单的用户信息管理,不需要DDD
public class User {
private UserId id; // 不需要值对象
private Email email; // 不需要值对象
private PhoneNumber phone; // 不需要值对象

// 简单的CRUD,不需要复杂的业务行为
}

// ✅ 简单场景用简单方案
public class User {
private Long id;
private String email;
private String phone;

// getter/setter即可
}

原则: 只在复杂业务系统中使用DDD

2. 盲目追求纯粹的领域模型

误区: 领域层完全不依赖任何框架

1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 过于纯粹,失去了框架的便利性
public class Order {
// 不使用JPA注解,手动管理持久化
// 不使用Spring的@Transactional,手动管理事务
// 结果:代码复杂度大增
}

// ✅ 实用主义,合理使用框架
public class Order {
// 可以使用少量框架特性,提高开发效率
// 关键是保持业务逻辑的纯粹性
}

原则: 实用主义,平衡纯粹性和开发效率

3. 忽视性能考量

误区: 每次都加载完整聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ 性能问题:查询列表时加载完整聚合
public List<OrderDTO> getOrderList(UserId userId) {
List<Order> orders = orderRepository.findByUserId(userId);
// 每个Order都加载了所有OrderItem,非常慢
return orders.stream()
.map(OrderDTO::from)
.collect(Collectors.toList());
}

// ✅ 使用CQRS,查询侧优化
public List<OrderListDTO> getOrderList(UserId userId) {
// 使用优化的SQL,只查询需要的字段
return orderQueryService.queryOrderList(userId);
}

原则: 命令侧使用领域模型,查询侧使用CQRS优化

4. 与现有框架的冲突

误区: DDD的聚合与JPA的实体映射冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 问题:JPA要求实体有无参构造函数和setter
// 但DDD要求聚合根通过工厂方法创建,不暴露setter

// 解决方案1:使用包级可见的构造函数
@Entity
public class Order {
// 包级可见,给JPA使用
Order() {}

// 工厂方法,给业务代码使用
public static Order create(...) {
// ...
}
}

// 解决方案2:使用Data Mapper模式
// 领域对象和持久化对象分离
public class Order { // 领域对象,无JPA注解
// ...
}

@Entity
public class OrderPO { // 持久化对象,有JPA注解
// ...
}

总结

DDD核心思想

  1. 以业务为中心: 软件模型直接反映业务领域
  2. 统一语言: 技术人员和业务人员使用相同的术语
  3. 分而治之: 通过限界上下文划分复杂领域
  4. 充血模型: 业务逻辑在领域对象内部

DDD的关键概念

战略设计:

  • 领域(Domain)
  • 子域(Subdomain):核心域、支撑域、通用域
  • 限界上下文(Bounded Context)
  • 上下文映射(Context Map)

战术设计:

  • 实体(Entity):有唯一标识
  • 值对象(Value Object):不可变,无标识
  • 聚合(Aggregate):保证一致性的边界
  • 领域服务(Domain Service):跨聚合的业务逻辑
  • 领域事件(Domain Event):实现解耦和最终一致性
  • 仓储(Repository):聚合的持久化
  • 工厂(Factory):复杂对象的创建

DDD的适用场景

适合使用DDD:

  • 业务逻辑复杂,规则多变
  • 需要长期维护的系统
  • 团队规模较大,需要清晰的边界
  • 微服务架构,需要划分服务边界

不适合使用DDD:

  • 简单的CRUD应用
  • 原型项目或短期项目
  • 技术驱动型项目(如工具类系统)
  • 团队对DDD理解不足

学习路径建议

  1. 理解核心概念: 先掌握实体、值对象、聚合等基本概念
  2. 实践小项目: 从简单的业务场景开始练习
  3. 阅读经典案例: 学习开源项目中的DDD实践
  4. 与团队协作: DDD强调统一语言,需要团队共识
  5. 持续优化: 根据业务变化持续重构领域模型

参考资料

  • 《领域驱动设计:软件核心复杂性应对之道》 - Eric Evans
  • 《实现领域驱动设计》 - Vaughn Vernon
  • 《领域驱动设计精粹》 - Vaughn Vernon
  • Microsoft DDD文档
  • Martin Fowler - DDD

相关文章: