بنقرة واحدة
entity
在DDD中具有唯一身份标识和生命周期的对象,通过身份而非属性值相等判断。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
在DDD中具有唯一身份标识和生命周期的对象,通过身份而非属性值相等判断。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
以聚合根为边界,包含多个相关Entity和ValueObject的集合。保证数据一致性和事务边界。
封装复杂对象和聚合的创建过程,将创建职责从领域对象中剥离,保证聚合创建时的不变量满足。
没有身份标识,通过属性值判断相等的对象。不可变,通常代表领域中的度量或描述。
命令查询责任分离,将数据的写入操作和读取操作分别用不同的模型处理,优化各自的性能。
将DDD战略设计应用于微服务架构,限界上下文指导服务拆分,领域事件实现服务间通信。
对比 MVC 模式与 DDD 方法论的差异,分析 MVC 在复杂业务下的局限性,帮助团队判断何时应采用 DDD。
| name | 实体 (Entity) |
| description | 在DDD中具有唯一身份标识和生命周期的对象,通过身份而非属性值相等判断。 |
Eric Evans 在蓝皮书中将实体定义为:拥有贯穿时间与不同表现形式的、独特身份的对象("Objects that have a distinct identity that runs through time and different representations")。两个实体即使属性完全相同,只要身份标识不同,它们就是不同的对象。
核心特征:
对比值对象:
应该是Entity的:
不应该是Entity的:
public class User {
private final String userId; // 身份标识(不可变)
private String name; // 属性(可变)
private String email; // 属性(可变)
public User(String userId, String name, String email) {
this.userId = userId;
this.name = name;
this.email = email;
}
// 通过身份判断相等,不通过属性值
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User)) return false;
User user = (User) o;
return Objects.equals(userId, user.userId); // 只比对 ID
}
@Override
public int hashCode() {
return Objects.hash(userId); // 只用 ID 计算哈希
}
}
// 两个用户,相同属性但不同ID → 不相等
User user1 = new User("U001", "John", "john@example.com");
User user2 = new User("U002", "John", "john@example.com");
assertNotEquals(user1, user2); // 不相等,因为 ID 不同
public class Order {
private final String orderId;
private OrderStatus status;
private List<OrderLine> items;
private double totalAmount;
private Instant createdAt;
private Instant modifiedAt;
// 创建
public static Order create(String customerId, List<OrderLine> items) {
Order order = new Order(UUID.randomUUID().toString(), items);
order.status = OrderStatus.PENDING;
order.createdAt = Instant.now();
return order;
}
// 状态变化
public void pay() {
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("Cannot pay order in status: " + status);
}
this.status = OrderStatus.PAID;
this.modifiedAt = Instant.now();
}
public void ship() {
if (status != OrderStatus.PAID) {
throw new IllegalStateException("Cannot ship unpaid order");
}
this.status = OrderStatus.SHIPPED;
this.modifiedAt = Instant.now();
}
public void deliver() {
if (status != OrderStatus.SHIPPED) {
throw new IllegalStateException("Cannot deliver unshipped order");
}
this.status = OrderStatus.DELIVERED;
this.modifiedAt = Instant.now();
}
}
// 订单的生命周期
Order order = Order.create("C001", items);
order.pay(); // PENDING → PAID
order.ship(); // PAID → SHIPPED
order.deliver(); // SHIPPED → DELIVERED
public class User {
private final String userId; // 不可变
private String name; // 可变
private String email; // 可变
public void updateProfile(String newName, String newEmail) {
this.name = newName;
this.email = newEmail;
}
}
// 实体的属性可以改变,但 ID 不变
User user = new User("U001", "John", "john@example.com");
user.updateProfile("Jane", "jane@example.com");
// 仍然是同一个用户,只是属性改变了
assertEquals("U001", user.getUserId());
// ❌ 使用自增数字(可能重用)
public class User {
private long id; // 1, 2, 3... 不够唯一
}
// ✅ 使用 UUID(全局唯一)
public class User {
private String userId = UUID.randomUUID().toString();
}
// ✅ 使用业务标识符(有含义)
public class Product {
private String sku; // 库存保留单位,业务上唯一
}
public class BankAccount {
private final String accountId;
private double balance;
// ❌ 不保护不变式
public void setBalance(double balance) {
this.balance = balance; // 允许负数!
}
// ✅ 保护不变式
public void withdraw(double amount) throws InsufficientFundsException {
if (balance < amount) {
throw new InsufficientFundsException();
}
this.balance -= amount;
}
public void deposit(double amount) throws InvalidAmountException {
if (amount <= 0) {
throw new InvalidAmountException();
}
this.balance += amount;
}
}
// ❌ 混乱的属性
public class User {
private String firstName;
private String lastName;
private String emailValue;
private String emailDomain;
}
// ✅ 用值对象组织
public class User {
private final String userId;
private FullName name; // 值对象:firstName + lastName
private Email email; // 值对象:封装邮箱逻辑
private Address address; // 值对象:封装地址
}
public class Order {
private final String orderId;
private Customer customer; // 引用,不嵌入
private List<OrderLine> items; // 从属(聚合根)
// ❌ 不要直接修改 items
// public List<OrderLine> getItems() { return items; }
// ✅ 提供受控的操作
public void addItem(Product product, int quantity) {
OrderLine line = new OrderLine(product, quantity);
this.items.add(line);
}
public void removeItem(String productId) {
items.removeIf(line -> line.getProduct().getId().equals(productId));
}
}
public class Order {
private final String orderId;
private String customerId;
private OrderStatus status;
private List<OrderLine> items;
private Money totalAmount;
private Instant createdAt;
private Instant modifiedAt;
public Order(String orderId, String customerId) {
this.orderId = orderId;
this.customerId = customerId;
this.status = OrderStatus.PENDING;
this.items = new ArrayList<>();
this.createdAt = Instant.now();
}
public void addItem(Product product, int quantity) {
if (quantity <= 0) {
throw new IllegalArgumentException("Quantity must > 0");
}
OrderLine line = new OrderLine(product, quantity);
items.add(line);
recalculateTotal();
}
public void pay() {
if (status != OrderStatus.PENDING) {
throw new IllegalStateException("Can only pay pending orders");
}
this.status = OrderStatus.PAID;
this.modifiedAt = Instant.now();
}
private void recalculateTotal() {
totalAmount = items.stream()
.map(OrderLine::getTotal)
.reduce(Money.ZERO, Money::add);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Order)) return false;
Order order = (Order) o;
return Objects.equals(orderId, order.orderId);
}
@Override
public int hashCode() {
return Objects.hash(orderId);
}
}
from dataclasses import dataclass
from enum import Enum
from datetime import datetime
from typing import List
class OrderStatus(Enum):
PENDING = "PENDING"
PAID = "PAID"
SHIPPED = "SHIPPED"
DELIVERED = "DELIVERED"
@dataclass
class OrderLine:
product_id: str
quantity: int
unit_price: float
def get_total(self) -> float:
return self.quantity * self.unit_price
class Order:
def __init__(self, order_id: str, customer_id: str):
self.order_id = order_id # 身份标识
self.customer_id = customer_id
self.status = OrderStatus.PENDING
self.items: List[OrderLine] = []
self.total_amount = 0.0
self.created_at = datetime.now()
self.modified_at = None
def add_item(self, product_id: str, quantity: int, unit_price: float):
if quantity <= 0:
raise ValueError("Quantity must > 0")
self.items.append(OrderLine(product_id, quantity, unit_price))
self._recalculate_total()
def pay(self):
if self.status != OrderStatus.PENDING:
raise ValueError(f"Cannot pay order in status {self.status}")
self.status = OrderStatus.PAID
self.modified_at = datetime.now()
def _recalculate_total(self):
self.total_amount = sum(item.get_total() for item in self.items)
def __eq__(self, other):
# 通过 ID 判断相等
if not isinstance(other, Order):
return False
return self.order_id == other.order_id
def __hash__(self):
return hash(self.order_id)
在实际工程中,实体的代码形态可以按携带业务逻辑的多少分为四种,选型直接影响领域层的厚度与可维护性。
术语对照:英文 DDD 圈通常只区分两种——Anemic Domain Model(贫血领域模型,Fowler 视为反模式) 与 Rich Domain Model(充血模型)。下表"四种血液模型"是中文工程社区的进一步细化划分;阅读 Evans / Vernon 原著时,请把"贫血 / 失血"都对应到 Fowler 的 Anemic 概念,不要混淆。
| 形态 | 包含内容 | 评价 |
|---|---|---|
| 失血模型 | 仅有数据字段和 getter/setter(Java 中的 POJO) | 不算领域对象,只是数据容器 |
| 贫血模型 | 数据 + 不依赖持久层的业务逻辑;依赖持久层的业务逻辑放到领域服务中 | 工程推荐,平衡可维护性与充血度 |
| 充血模型 | 数据 + 所有业务逻辑(含依赖持久层的) | 领域对象最"纯",但与持久层耦合 |
| 胀血模型 | 数据 + 业务逻辑 + 与业务无关的其他逻辑(事务、授权、日志等) | 应避免,违反单一职责 |
// ❌ 失血模型:只有字段,业务逻辑全部外泄
public class Order {
private Long id;
private Integer status;
private BigDecimal amount;
// 50 行 getter/setter ...
}
public class OrderService {
public void pay(Long orderId) {
Order o = orderDao.get(orderId);
if (o.getStatus() != 0) throw new RuntimeException("状态错误");
o.setStatus(1);
orderDao.update(o);
}
}
// ✅ 贫血模型:数据 + 核心业务规则,持久化交给仓储
public class Order {
private OrderId id;
private OrderStatus status;
private Money amount;
public void pay() { // 业务规则在实体内
if (status != OrderStatus.PENDING) {
throw new DomainException("只有待支付订单可以支付");
}
this.status = OrderStatus.PAID;
}
}
public class OrderAppService {
public void pay(OrderId id) {
Order order = orderRepo.findById(id);
order.pay(); // 调业务方法
orderRepo.save(order); // 持久化由仓储负责
}
}
// 充血模型:持久化也在实体内(少用)
public class Order {
public void pay() {
if (status != OrderStatus.PENDING) { ... }
this.status = OrderStatus.PAID;
OrderRepository.save(this); // 持久化耦合进实体
}
}
// ❌ 胀血模型:一锅端,权限/事务/日志全塞进来
public class Order {
@Transactional
@RequiresPermission("ORDER_PAY")
public void pay(User operator) {
AuditLog.log("..."); // ← 和业务无关的东西
// ...
}
}
经验建议:
| 特性 | Entity | Value Object |
|---|---|---|
| 身份 | 有唯一 ID | 无 ID |
| 相等性 | 基于 ID | 基于属性值 |
| 可变性 | 通常可变 | 不可变 |
| 生命周期 | 有明确生命周期 | 无生命周期 |
| 例子 | User, Order | Money, Email, Address |
实体经常被误用为 DTO(数据传输对象),导致领域层退化为"带方法的字段袋"。二者必须严格区分:
| 维度 | Entity(实体) | DTO(数据传输对象) |
|---|---|---|
| 所属层 | 领域层 | 接口层 / 应用层 |
| 目的 | 承载业务规则与状态转换 | 在层与层、进程与进程之间搬运数据 |
| 行为 | 有业务方法(pay、ship、approve) | 通常只有字段 + getter/setter |
| 不变量 | 自我保护、构造即合法 | 不维护不变量,由发送方负责 |
| 身份 | 有领域标识(OrderId) | 通常无标识 |
| 序列化 | 不直接序列化(避免暴露内部结构) | 为序列化而生(JSON、ProtoBuf) |
| 生命周期 | 与业务对象一致,长久存在 | 一次请求/响应即弃 |
判别关键:如果一个类既要被序列化为 HTTP 响应,又要承载业务规则,几乎可以肯定是把 Entity 和 DTO 揉在了一起——拆开。
// ❌ Entity 与 DTO 混用
@RestController
public class OrderController {
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable String id) {
return orderRepository.findById(id); // 直接把 Entity 序列化返回
}
}
// ✅ 用 DTO 隔离边界
@RestController
public class OrderController {
@GetMapping("/orders/{id}")
public OrderDTO getOrder(@PathVariable String id) {
Order order = orderRepository.findById(id);
return OrderDTO.from(order); // 在边界处转换
}
}
❌ 用属性判等——equals 比对 name + email,导致两个不同 userId 的 User 因属性相同被认为相等
→ 实体的相等性永远基于身份标识,与任何属性无关
❌ 全字段 setter 暴露——实体退化为数据袋,业务规则散落到 Service → 暴露业务方法(pay、ship、cancel),让状态变化必经规则校验
❌ 持久化可计算字段——把 totalAmount 字段化并要求外部维护一致性
→ 能从其他字段算出的不必字段化;必须字段化时由聚合根内部计算与维护
Entity 的核心:
最佳实践:
Entity 是 DDD 的核心概念,正确使用能大幅提高设计质量。