2. 领域驱动设计(DDD)基础:实体、值对象、聚合、限界上下文的概念与作用
领域驱动设计(Domain-Driven Design,DDD)是应对复杂业务逻辑的核心方法论。它提供了一套精确的战术模式(实体、值对象、聚合)和战略模式(限界上下文),帮助架构师在业务系统中划定清晰的模块边界,并确保接口设计能够反映真实的业务语义。
核心认知: DDD 不是技术框架,而是一种建模思维。它要求团队以业务专家的语言(通用语言)来构建软件模型,从而避免技术实现与业务需求之间的鸿沟。
2.1 实体(Entity)
实体是具有唯一标识且其状态会随时间变化的对象。在业务系统中,一个实体必须能够被唯一地识别和追踪,即使它的所有属性都发生了变化,它仍然是同一个对象。
- 核心特征: 拥有唯一标识(ID),如用户ID、订单ID、合同编号。
- 可变性: 属性可以被修改,但身份不变。例如,用户修改了手机号,该用户实体依然是同一个用户。
- 业务作用: 承载业务生命周期和状态流转,是业务操作的主要对象。
| 对比维度 | 实体(Entity) | 值对象(Value Object) |
|---|---|---|
| 标识 | 有唯一标识(ID) | 无标识,由属性值定义 |
| 可变性 | 可变(Mutable) | 不可变(Immutable) |
| 相等性 | 通过 ID 判断 | 通过所有属性值判断 |
| 生命周期 | 有独立生命周期 | 依附于实体,无独立生命周期 |
| 典型示例 | 订单、客户、产品 | 地址、金额、时间段 |
2.2 值对象(Value Object)
值对象用于描述实体的属性或特征,它没有唯一标识,只通过其属性值来定义。值对象是不可变的,一旦创建就不能被修改;如果需要不同的值,则创建一个新的值对象。
- 不可变性: 创建后属性不可变,保证了线程安全和逻辑一致性。
- 无副作用: 对值对象的操作应返回新实例,而非修改自身。
- 业务语义: 将多个相关属性封装为一个整体,提升模型表达力。例如,将“省、市、区、街道”封装为
Address值对象。
// 值对象示例:金额
public class Money {
private final BigDecimal amount;
private final String currency;
public Money(BigDecimal amount, String currency) {
this.amount = amount;
this.currency = 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);
}
// 通过所有属性判断相等性
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() { /* ... */ }
}
2.3 聚合(Aggregate)
聚合是一组相关对象的集合,它将实体和值对象封装在一个一致性边界内。聚合通过一个聚合根(Aggregate Root)来对外暴露操作,外部对象只能通过聚合根来访问聚合内部的对象,从而保证数据的一致性和业务规则的不变性。
- 聚合根: 聚合中唯一的实体,是外部访问聚合的唯一入口。例如,
订单是聚合根,订单项是聚合内的实体。 - 一致性边界: 聚合内部的所有变更必须在一次事务中完成,确保业务规则不被破坏。
- 引用规则: 外部对象只能持有对聚合根的引用,不能直接引用聚合内部的实体。
设计原则: 聚合应该尽量小,只包含必须保持一致的业务对象。过大的聚合会导致事务冲突和性能问题。
2.4 限界上下文(Bounded Context)
限界上下文是 DDD 战略设计的核心,它定义了模型应用的边界。在一个限界上下文内部,通用语言(Ubiquitous Language)是统一的,模型具有明确的含义;不同的限界上下文之间通过上下文映射(Context Map)进行集成。
- 边界划分: 每个限界上下文对应一个独立的业务子域,拥有自己的模型和数据库。
- 通用语言: 团队与业务专家使用统一的术语进行沟通,这些术语直接映射到代码中的模型。
- 独立演进: 不同限界上下文可以独立开发、部署和扩展,降低耦合度。
常见误区: 不要试图创建一个“大一统”的全局模型。不同业务上下文对同一个概念的理解可能完全不同(例如,“客户”在销售上下文和售后上下文中的属性与行为差异很大),强行统一会导致模型臃肿且难以维护。
2.5 战术模式与战略模式的关系
在 DDD 实践中,战略设计(限界上下文、上下文映射)决定了系统的宏观模块边界,而战术设计(实体、值对象、聚合、领域服务等)则指导了边界内部的微观模型实现。两者相辅相成,缺一不可。
| 设计层次 | 核心模式 | 关注点 | 产出物 |
|---|---|---|---|
| 战略设计 | 限界上下文、上下文映射 | 系统边界、团队组织、集成策略 | 上下文地图、模块划分 |
| 战术设计 | 实体、值对象、聚合、领域服务 | 业务逻辑、数据一致性、模型表达 | 领域模型、代码实现 |
实战建议: 在开始编码之前,先与业务专家一起绘制限界上下文地图,明确每个上下文的职责和交互方式。然后,在每个上下文内部,使用实体和值对象来构建领域模型,并通过聚合来保证业务规则的一致性。这种自上而下的设计方法,能够有效避免“大泥球”架构,为后续的接口版本管理奠定坚实的业务基础。