Skip to content

L5 - 实体与值对象(Entities & Value Objects) ​

Model 在 BC 内的两种形态——Entity 有身份(跨越变化),Value Object 是描述(不可变可替换)。判断决策树:业务关心"区分实例"吗?关心 = Entity,不关心 = Value Object。

一句话 ​

Entity = 身份 + 生命周期 + 可变状态;Value Object = 描述 + 不可变 + 可替换。两者的根本区别是"业务是否关心实例区分"。

这本书在这一层说了什么 ​

本层融合 IDDD Ch 5 + Ch 6。

Vernon Ch 5 关键论断:

"We design a domain concept as an Entity when we care about its individuality, when distinguishing it from all other objects in a system is a mandatory constraint. An Entity is a unique thing and is capable of being changed continuously over a long period of time. Changes may be so extensive that the object might seem much different from what it once was. Yet, it is the same object by identity."

Evans Reference(Vernon 引用):

"When an object is distinguished by its identity, rather than its attributes, make this primary to its definition in the model. Keep the class definition simple and focused on life cycle continuity and identity. Define a means of distinguishing each object regardless of its form or history. ... The model must define what it means to be the same thing."

Vernon Ch 5 警告(CRUD 误用):

"There are times when an Entity is not the appropriate modeling tool to reach for. Misappropriated use happens far more often than many are aware. Often a concept should be modeled as a Value. If this is a disagreeable notion, it might be that DDD doesn't fit your business needs."

核心论点 5 个:

  1. Why We Use Entities — 为什么用 Entity
  2. Unique Identity — 4 大身份生成方式
  3. Discovering Entities — 行为优先 + Tell-Don't-Ask
  4. Value Characteristics — 值对象 6 大特征
  5. Persisting Value Objects — 4 种持久化方式

是什么 ​

1. Entity(实体) ​

Vernon 定义:

"We design a domain concept as an Entity when we care about its individuality."

Evans Reference 定义(Vernon 引用):

"When an object is distinguished by its identity, rather than its attributes, make this primary to its definition in the model."

Entity 的 3 大特征:

1. 唯一身份(Unique Identity)
   - 跨生命周期保持
   - 即使属性全变,身份不变

2. 可变状态(Mutability)
   - 长期演化
   - 属性持续变化

3. 连续性(Continuity)
   - 同一对象(同一身份)
   - 即使"看似不同"

Vernon Ch 5 关键例子("我还是我"):

"An Entity is a unique thing and is capable of being changed continuously over a long period of time. Changes may be so extensive that the object might seem much different from what it once was. Yet, it is the same object by identity."

2. 4 大身份生成方式 ​

Vernon Ch 5 §Unique Identity 详解:

方式 1: User Provides Identity(用户提供) ​

特征:
  - 用户输入(如邮箱、身份证号)
  - 业务相关
  - 需要去重检查
  
例子:
  - 邮箱(User 注册)
  - 手机号(订单联系人)
  - 身份证号(金融业务)
  
风险:
  - 用户输入冲突 → 需要去重
  - 用户输入变更 → 身份变更(违反 Entity 同一性)

方式 2: Application Generates Identity(应用生成) ​

特征:
  - 应用运行时生成
  - 唯一性保证
  
方案:
  - UUID(Universally Unique Identifier)
  - Snowflake(Twitter 雪花算法)
  - ULID(排序 ID)
  
优点:
  - 离线生成(无须数据库)
  - 全局唯一
  - 不可枚举
  
缺点:
  - 无业务含义
  - 用户难记

方式 3: Persistence Mechanism Generates Identity(持久化机制生成) ​

特征:
  - 数据库自增 ID
  - 序列(Sequence)
  
优点:
  - 简单
  - 性能好
  
缺点:
  - 业务量泄露(自增 ID 暴露)
  - 难以分布式
  - 不可跨系统

方式 4: Another Bounded Context Assigns Identity(另一个 BC 分配) ​

特征:
  - 跨 BC 身份来自其他 BC
  - 例:订单 ID 来自 Identity BC(用户 ID)
  
风险:
  - 强依赖
  - BC 挂了实体无法创建
  
Vernon 建议:避免这种(用应用生成 UUID 替代)

3. Surrogate Identity(代理身份) ​

Vernon 概念: 当业务身份不稳定(如订单号变更)时,用一个不变的代理 ID。

例:
  业务身份 = 订单号(业务可读,但可能变更)
  代理身份 = orderId(UUID,永不变)
  
使用:
  - 内部用 orderId(跨生命周期稳定)
  - 外部用订单号(人类可读)

4. Identity Stability(身份稳定性) ​

Vernon 关键警告:

"An Entity's identity must be stable throughout its lifetime."

反模式:

  • 用"业务可读字段"当身份(业务字段可能改)
  • 用"组合字段"当身份(组合可能变)

修复:用 UUID 作代理 ID + 业务字段单独管理。

5. Value Object(值对象) ​

Vernon Ch 6 关键定义:

"When you care only about the attributes of an element of the model, classify it as a Value Object."

Evans Reference:

"Treat the Value Object as if it were the Conceptual Whole—as a single unit... Value Objects are immutable."

6. Value Object 的 6 大特征 ​

Vernon Ch 6 §Value Characteristics:

特征含义
Measures, Quantifies, or Describes描述、量化、衡量(无身份)
Immutable不可变
Conceptual Whole概念整体(不是字段组合)
Replaceability可替换(new 一个相同的)
Value Equality值相等(不是引用相等)
Side-Effect-Free Behavior行为无副作用

Vernon 关键论断:

"It is the unique identity and mutability characteristics that set Entities apart from Value Objects."

7. Entity vs Value Object 决策树 ​

决策树:
  Q1: 业务关心"区分不同的实例"吗?
    是 → Entity
    否 ↓
  Q2: 有生命周期吗?(创建/修改/删除)
    是 → Entity
    否 → Value Object
  
  附加问题:
  Q3: 业务说"金额是 100"?
    是 → Value Object
  Q4: 业务说"用户 123 是同一个"?
    是 → Entity

Vernon 例子:

Entity 例子:
  - User(业务关心"哪个用户")
  - Order(业务关心"哪个订单")
  - Product(业务关心"哪个产品")

Value Object 例子:
  - Money(金额 + 币种 = 整体)
  - Address(地址 = 街道 + 城市 + 邮编)
  - DateRange(日期范围 = 开始 + 结束)
  - PhoneNumber(手机号 = 区号 + 号码)

8. Value Object 的"无副作用行为" ​

Vernon 关键论断:

"Side-Effect-Free Behavior."

反例(有副作用):

java
// 错:修改内部状态
public class Money {
  public void add(Money other) {
    this.amount += other.amount;  // 副作用!
  }
}

正确(返回新对象):

java
// 对:返回新 Money
public class Money {
  public Money add(Money other) {
    return new Money(this.amount + other.amount, this.currency);
  }
}

9. Value Object 的相等性 ​

java
// 值相等(不是引用相等)
public class Money {
  private final BigDecimal amount;
  private final String currency;
  
  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Money)) return false;
    Money other = (Money) o;
    return amount.equals(other.amount) && currency.equals(other.currency);
  }
  
  @Override
  public int hashCode() {
    return Objects.hash(amount, currency);
  }
}

10. Value Object 的 4 种持久化方式 ​

Vernon Ch 6 §Persisting Value Objects:

方式 1: ORM Embedded(嵌入字段) ​

例:Money 作为 Order 的嵌入字段
  - Order 表 = orderId, customerId, totalAmount, totalCurrency
  
优点:最简单
缺点:值对象字段全平铺到主表

方式 2: Serialized Column(序列化为单列) ​

例:JSON 序列化 Address 到单列
  - User 表 = userId, name, address_json
  
优点:值对象结构自由
缺点:难查询字段

方式 3: Database Entity(独立表 + 外键) ​

例:Address 独立表
  - User 表 = userId, addressId
  - Address 表 = addressId, street, city
  
优点:可独立查询
缺点:本质上是 Entity(不是 Value Object)

方式 4: Join Table(多对多关系) ​

例:User 多 PhoneNumber
  - User 表 = userId
  - PhoneNumber 表 = phoneNumberId, number, type
  - User_PhoneNumber 表 = userId, phoneNumberId
  
优点:支持多值
缺点:复杂

Vernon 关键警告:

"Reject Undue Influence of Data Model Leakage."

根因:数据库思维(行/列)渗透到领域对象 → Value Object 失去"概念整体"。

修复:让 Value Object 是 POJO,持久化由 ORM 处理,不让 ORM 注解污染领域对象。

为什么 ​

为什么需要 Entity? ​

根因:业务世界有"持续存在的事物"——用户、订单、产品。这些事物身份不变但状态持续变化。

反例(无 Entity):

  • 用数据库行表示订单 → 没有"订单对象"的连续性
  • 订单修改 = 删除旧行 + 插入新行 → 历史断裂
  • 难以追踪"订单变更"

Entity 的关键价值:身份跨越时间,连接所有状态变化。

为什么需要 Value Object? ​

根因:业务世界有"概念整体"——金额、地址、时间段。这些概念无身份但可替换。

反例(Value 当 Entity):

java
// 错:金额是 Entity
public class Money {
  private Long id;  // 浪费!
  private BigDecimal amount;
}

Value Object 的关键价值:

  • 不可变 = 线程安全
  • 无副作用行为 = 可推理
  • 可替换 = 简化设计

Vernon 关键论断:

"Many things in the domain do not need to be tracked as individual instances."

为什么 Entity 强调"行为优先"? ​

Vernon 关键洞察:

"Rather than focusing on the attributes or even the behavior, strip the Entity object's definition down to the most intrinsic characteristics, particularly those that identify it or are commonly used to find or match it. Add only behavior that is essential to the concept and attributes that are required by that behavior. [Evans, p. 93]"

Vernon 论断 = 反"贫血模型":

  • 不要上来就定义所有属性
  • 先识别"本质特征"(身份)
  • 再加"本质行为"(与身份关联)

为什么 Value Object 强调"不可变"? ​

Vernon 关键论断:

"Value Objects are immutable."

根因 1:并发安全

  • 不可变 = 多线程共享无冲突

根因 2:业务一致性

  • 不可变 = 创建后业务规则稳定

根因 3:替换语义

  • "修改" = 创建新值对象(业务意义清晰)

怎么做 ​

Step 1:识别 Entity 候选 ​

操作:
  1. 与业务专家工作坊
  2. 列出业务关心的"持续存在的事物"
  3. 每个事物问"业务区分不同实例吗?"
  4. 是 → Entity 候选
  
产出:Entity 候选清单

Step 2:选择身份生成方式 ​

决策树:
  Q1: 业务可读字段能保证唯一且不变吗?
    是 → User Provides Identity(业务身份)
    否 ↓
  Q2: 需要跨 BC 唯一吗?
    是 → UUID(应用生成)
    否 ↓
  Q3: 简单单系统?
    是 → 数据库自增
    否 → UUID

Step 3:设计 Entity 行为 ​

原则(Evans + Vernon):
  - 行为优先(不是属性优先)
  - Tell-Don't-Ask(实体自己处理)
  - 不暴露 setter(修改通过行为)

Step 4:识别 Value Object 候选 ​

操作:
  1. 列出所有"概念整体"(金额、地址、时间段)
  2. 问"业务关心实例区分吗?"
  3. 否 → Value Object
  4. 设为不可变(无 setter)

Step 5:选择持久化方式 ​

决策树:
  Q1: 值对象是简单字段组合?
    是 → Embedded(嵌入主表)
    否 ↓
  Q2: 字段需要单独查询?
    是 → JSON Column
    否 → Embedded
  Q3: 是多值关系?
    是 → Join Table

什么时候用 / 什么时候不用 ​

用 Entity 的 2 大场景 ​

场景关键判断
业务关心"区分实例"必有 Entity
对象有长期生命周期必有 Entity

用 Value Object 的 2 大场景 ​

场景关键判断
业务只关心"属性集合"用 Value Object
业务有"概念整体"用 Value Object(金额、地址等)

不用 Value Object 的 1 大场景 ​

场景原因
对象有身份 + 生命周期必须 Entity

看不见的地方 ​

失效模式 1:贫血 Entity(Anemic Domain Model) ​

症状:Entity 只有 getter/setter,所有业务行为在 Service → Entity 只是"数据袋"。

根因:开发者按"数据库思维"建模(Entity = 表),不按"业务思维"建模(Entity = 行为 + 状态)。

检测:问"Entity 有几个行为方法?"——少于 3 个 = 贫血嫌疑。

修复:行为下沉到 Entity;Service 只做跨实体编排。

失效模式 2:身份不稳定 ​

症状:用业务可读字段(如订单号)当 Entity 身份 → 业务字段改了 Entity 身份也变 → 历史断裂。

根因:未理解 Entity 身份必须跨生命周期稳定。

修复:用 UUID 作代理 ID + 业务字段单独管理。

失效模式 3:身份生成导致业务泄露 ​

症状:用自增 ID → 竞争对手枚举 → 业务量曝光。

根因:未考虑身份生成的副作用。

修复:内部用自增(性能),对外用 UUID(不可枚举)。

失效模式 4:Value Object 加 setter ​

症状:Value Object 用 setter 修改 → 不可变破坏 → 业务规则崩溃。

根因:开发者习惯 setter。

修复:Value Object 用构造函数 + with 方法返回新对象。

失效模式 5:Value Object 引用 Entity ​

症状:Money 值对象包含 User 实体 → 值对象庞大 + 强耦合。

根因:边界模糊。

修复:Value Object 只包含值,不引用 Entity。

失效模式 6:Entity 承担过多角色 ​

症状:User 实体同时是"客户 + 员工 + 管理员" → 改一处影响所有角色。

根因:未识别 Entity 的不同角色。

修复:按角色拆 BC,每 BC 有自己的 User Entity。

失效模式 7:Value Object 当 Entity 持久化 ​

症状:Value Object 有独立表 + 主键 → 持久化复杂。

根因:用 ORM 思维建模。

修复:Value Object 作为嵌入字段(Embedded),与所属 Entity 同表。

失效模式 8:业务身份 vs 代理 ID 混用 ​

症状:有时用 orderId(UUID)有时用订单号 → 一致性破坏。

根因:未明确"内部 ID" vs "外部 ID"。

修复:内部一律 UUID + 业务 ID 单独字段管理。

相关节点 ​

骨架 A — 世界观 ​

节点关联解释
wonder-02-emergence-action强Entity = "涌现作为"的最小单位——身份稳定的"作为者"
wonder-03-emergence-time强Entity 身份 = "时"的锚点——跨越变化的同一性

骨架 B — 工程实践 ​

节点关联解释
change-management中Entity 身份 = 变更管理的锚点——变更不破坏身份

与 IDDD 其他层对位 ​

层关系
L6 聚合Entity 是聚合根的候选
L7 战术深入Entity 的行为 / Value Object 的设计原则贯穿 L7

与 EDM 对位 ​

EDMIDDD L5
Ch 2 实体事件L5 Entity 身份作为事件 Key
Ch 2 事件 ValueL5 Value Object 思想应用

状态 ​

L5 概念递进落地完成(基于原书 ch05 93KB / 14K 词 + ch06 96KB / 14K 词)。

  • 4 大身份生成方式(User / Application / Persistence / Another BC)
  • Value Object 6 大特征 + 4 种持久化方式
  • Entity vs Value Object 完整决策树
  • 8 个失效模式
  • 骨架 A/B + IDDD + EDM 完整映射

从名家方法论与工程化思路中蒸馏出自己的工程体系。