ワンクリックで
ワンクリックで
以聚合根为边界,包含多个相关Entity和ValueObject的集合。保证数据一致性和事务边界。
在DDD中具有唯一身份标识和生命周期的对象,通过身份而非属性值相等判断。
封装复杂对象和聚合的创建过程,将创建职责从领域对象中剥离,保证聚合创建时的不变量满足。
命令查询责任分离,将数据的写入操作和读取操作分别用不同的模型处理,优化各自的性能。
将DDD战略设计应用于微服务架构,限界上下文指导服务拆分,领域事件实现服务间通信。
对比 MVC 模式与 DDD 方法论的差异,分析 MVC 在复杂业务下的局限性,帮助团队判断何时应采用 DDD。
| name | 值对象 (Value Object) |
| description | 没有身份标识,通过属性值判断相等的对象。不可变,通常代表领域中的度量或描述。 |
Eric Evans 将值对象定义为:描述领域中某个事物特征的对象,没有概念上的身份("describes some characteristic of a thing")。两个值对象只要属性值相同,就被视为等价。
核心特征:
大多数情况下实体具有很多属性,这些属性一般都是平铺。但有的属性归类和聚合后能表达一个业务含义,就把这些属性封装成值对象,从而:
Address)替代多个原始字段String、BigDecimal一句话:值对象就是用来描述实体的特征。实体的单一属性也可以是值对象。
只封装一个值,但赋予它领域含义。通常基于字符串、整型、枚举等原始类型。
// ❌ 原始类型偏执
public class User {
private String email; // 只是个字符串,随便赋值
private String phone;
}
// ✅ 单一属性值对象
public final class Email {
private final String value;
public Email(String value) {
if (value == null || !value.matches("^[\\w.-]+@[\\w.-]+$")) {
throw new DomainException("邮箱格式不合法: " + value);
}
this.value = value.toLowerCase();
}
public String domain() {
return value.substring(value.indexOf('@') + 1);
}
@Override
public boolean equals(Object o) {
return (o instanceof Email) && value.equals(((Email) o).value);
}
@Override
public int hashCode() { return value.hashCode(); }
}
public enum SubscriptionStatus { // 枚举也是值对象
ACTIVE, CANCELLED, EXPIRED
}
public class User {
private final Email email; // 有自我验证
private final SubscriptionStatus status; // 业务语义清晰
}
把多个相关属性组合成一个概念整体。设计成 class(或 record / dataclass),包含多个属性,但没有 ID。值对象中可以嵌套值对象。
// ✅ 多属性值对象:Money(amount + currency)
public final class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) { ... }
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new DomainException("币种不一致");
}
return new Money(amount.add(other.amount), currency); // 返回新对象
}
}
// ✅ 多属性值对象:Address(嵌套 PostCode 值对象)
public final class Address {
private final String province;
private final String city;
private final String street;
private final PostCode postCode; // 嵌套值对象
// ...
}
// ✅ 多属性值对象:DateRange
public final class DateRange {
private final LocalDate start;
private final LocalDate end;
public DateRange(LocalDate start, LocalDate end) {
if (end.isBefore(start)) {
throw new DomainException("end 不能早于 start");
}
this.start = start;
this.end = end;
}
public boolean contains(LocalDate date) {
return !date.isBefore(start) && !date.isAfter(end);
}
public Duration length() { return Duration.between(start.atStartOfDay(), end.atStartOfDay()); }
}
from dataclasses import dataclass
from decimal import Decimal
# 单一属性值对象
@dataclass(frozen=True)
class Email:
value: str
def __post_init__(self):
if '@' not in self.value:
raise ValueError(f"Invalid email: {self.value}")
object.__setattr__(self, 'value', self.value.lower()) # frozen 下用 object.__setattr__
def domain(self) -> str:
return self.value.split('@')[1]
# 多属性值对象
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add(self, other: "Money") -> "Money":
if self.currency != other.currency:
raise ValueError("Currency mismatch")
return Money(self.amount + other.amount, self.currency)
// ✅ 值对象:Money
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("Cannot add different currencies");
}
return new Money(amount.add(other.amount), currency);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Money)) return false;
Money money = (Money) o;
return amount.equals(money.amount) && currency.equals(money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
}
// 使用
Money m1 = new Money(new BigDecimal("100"), Currency.USD);
Money m2 = new Money(new BigDecimal("100"), Currency.USD);
assertEquals(m1, m2); // 值相等
from dataclasses import dataclass
from decimal import Decimal
@dataclass(frozen=True) # 不可变
class Money:
amount: Decimal
currency: str
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Cannot add different currencies")
return Money(self.amount + other.amount, self.currency)
# 使用
m1 = Money(Decimal("100"), "USD")
m2 = Money(Decimal("100"), "USD")
assert m1 == m2 # 值相等
| 特性 | Entity | Value Object |
|---|---|---|
| 身份 | 有 ID | 无 ID |
| 相等性 | 基于 ID | 基于值 |
| 可变性 | 可变 | 不可变 |
| 示例 | User, Order | Money, Email, Address |
money.add(other) 返回新 Money)equals / hashCode 基于全部属性值对象不是越多越"DDD"。下列场景直接用原始类型反而更清晰:
description、remark、title),强行包成 Description 只增加噪音判据:能写出一句业务约束("邮箱必须含 @"、"金额非负"、"两个货币必须同种才能相加")的,值得包成 VO;写不出业务约束的,就是个字段。
❌ 给值对象加 ID——Money 加 id 字段以便存数据库
→ 值对象按值相等,没有概念身份;持久化时打平到所属实体的列即可
❌ 修改字段而非返回新对象——money.setAmount(x)
→ 操作必须返回新实例(money.add(other) 返回新 Money),保证不可变
❌ 构造时不校验——new Email("not-an-email") 也能通过
→ 构造即合法:所有领域约束在构造函数 / 工厂方法里强制校验
值对象简化了代码,提高了表达力与安全性。