DDD 系列:实体和值对象详解

上一篇文章中,我们通过事件风暴梳理了业务事件、命令和业务规则,也得到了一些领域模型的线索。

从这一篇开始,我们进入 DDD 的战术设计。先从两个最基础、也最容易混淆的概念说起:实体和值对象。

不少人第一次接触它们时,会直接按照数据库表来判断:有表、有主键的就是实体,没有单独建表的就是值对象。这个判断方式很省事,但也很容易把模型带偏。

实体和值对象的区别,不在于怎么存储,而在于业务如何判断"它还是不是原来的那个对象"。

一、什么是实体

实体是通过唯一标识区分,并且会在业务过程中持续变化的领域对象。

以订单为例。一笔订单从待支付变成已支付,再变成已完成,状态和内容都可能发生变化,但只要订单编号没有变,业务上就认为它还是同一笔订单。

所以实体通常有两个特点:

  • 有能够区分身份的唯一标识;
  • 有自己的生命周期,属性会随着业务行为发生变化。

需要注意,唯一标识不一定就是数据库自增主键。订单号、用户编号、设备编码,只要能在当前业务范围内稳定识别对象,都可以成为实体标识。

下面是一个简化的订单实体:

java 复制代码
/**
 * 订单实体
 *
 * 订单通过订单编号识别身份,支付状态会随着业务流程发生变化。
 */
public class Order {

    /** 订单编号,用于在业务范围内唯一识别一笔订单 */
    private final String orderNo;

    /** 订单当前状态 */
    private OrderStatus status;

    /**
     * 支付订单
     *
     * 只有待支付订单才能执行支付,状态变更必须通过业务方法完成,
     * 避免外部代码绕过业务规则直接修改订单状态。
     */
    public void pay() {
        if (status != OrderStatus.PENDING_PAYMENT) {
            throw new IllegalStateException("当前订单状态不允许支付");
        }
        this.status = OrderStatus.PAID;
    }
}

这里真正重要的不是 Order 有没有对应一张表,而是订单编号和支付状态在业务中的含义。

二、什么是值对象

值对象没有独立身份,它通过一组属性共同表达业务含义。

例如金额不能只用一个 BigDecimal 表示,因为 100 元和 100 美元显然不是一回事。金额和币种组合在一起,才是一个完整的业务概念。

值对象通常有下面几个特点:

  • 没有需要持续跟踪的唯一标识;
  • 属性相同,业务上就认为两个对象相等;
  • 创建后尽量保持不变,需要修改时创建新对象整体替换;
  • 可以在创建时完成格式、范围等规则校验。

下面定义一个金额值对象:

java 复制代码
/**
 * 金额值对象
 *
 * 金额必须和币种一起表达业务含义,对象创建后不可修改。
 */
public record Money(BigDecimal amount, Currency currency) {

    /**
     * 创建金额对象时统一校验基础规则,
     * 防止非法金额进入后续订单计算流程。
     */
    public Money {
        if (amount == null || amount.signum() < 0) {
            throw new IllegalArgumentException("金额不能为空且不能小于零");
        }
        if (currency == null) {
            throw new IllegalArgumentException("币种不能为空");
        }
    }

    /**
     * 金额相加不会修改当前对象,而是返回一个新的金额对象。
     */
    public Money add(Money other) {
        if (!currency.equals(other.currency())) {
            throw new IllegalArgumentException("不同币种不能直接相加");
        }
        return new Money(amount.add(other.amount()), currency);
    }
}

值对象并不等于"只放数据"。像金额相加、日期范围校验、手机号格式校验这类只依赖自身属性的业务规则,都可以放进值对象。

三、实体和值对象怎么判断

两者的核心区别可以先看这张表:

判断维度 实体 值对象
如何区分 看唯一标识 看全部关键属性
是否有生命周期 有,需要持续跟踪 没有独立生命周期
属性变化 身份不变,属性可以变化 通常创建新对象整体替换
相等判断 标识相同即可认为是同一对象 属性相同才认为相等
常见例子 订单、用户、合同 金额、地址、时间范围

实际建模时,可以连续问三个问题。

1. 业务是否关心它是谁

两个对象的属性完全相同,业务还需要区分它们吗?

比如两张同名、同价的订单,仍然是两笔不同订单,因此订单是实体。两个金额和币种都相同的金额对象,业务上通常不需要区分来源,因此金额适合作为值对象。

2. 是否需要跟踪它的变化

如果对象会经历创建、修改、失效等过程,并且业务需要知道变化前后仍是同一个对象,它更像实体。

如果业务只关心当前这组属性,不关心对象本身经历过什么,它更像值对象。

3. 修改时是更新还是替换

订单支付后,我们会更新这笔订单的状态,因为它仍是原来的订单。

收货地址发生变化时,更合适的做法是创建一个新地址,整体替换旧地址,而不是先改省份、再改城市。这样可以避免业务过程中出现"省份已经更新,城市还是旧值"的临时错误状态。

如果三个问题仍然得不出答案,就回到真实业务场景继续确认,不要根据类名或表结构硬猜。

四、同一个概念不一定永远是同一种模型

实体和值对象不是固定标签,同一个概念放在不同业务中,建模结果可能完全不同。

例如地址:

  • 在订单中,业务只关心下单时的省、市、区和详细地址,地址适合作为值对象;
  • 在地址簿中,用户可以新增、编辑、删除某一条地址,还要设置默认地址,此时每条地址需要被单独识别,更适合作为实体。

再比如商品:

  • 在商品中心,商品有独立编号和上下架生命周期,是实体;
  • 在历史订单中,商品名称、成交价等信息需要保留下单时的快照,这部分更像值对象。

所以不能背"地址就是值对象,商品就是实体"。模型的类型取决于当前限界上下文中的业务含义。

五、几个常见误区

1. 有数据库主键就是实体

值对象为了方便存储,也可能单独建表并带有技术主键。这个主键只是数据库实现细节,不代表业务需要通过它识别对象。

反过来,一个业务实体暂时没有落库,也不妨碍它成为实体。

2. 值对象必须只有一个字段

值对象可以包含多个字段。省、市、区、详细地址组合成地址,开始时间和结束时间组合成时间范围,都比散落的基础类型更能表达业务含义。

3. 值对象绝对不能变化

更准确地说,值对象自身不应该被逐项修改,但持有它的实体可以用新值对象替换旧值对象。

订单可以更换收货地址,但应该替换整个地址对象,而不是把地址对象暴露出去随意调用 setter。

4. Java 中叫 Entity 的类就是 DDD 实体

很多项目会把数据库映射类命名为 XxxEntity,它可能只是一个贫血的数据对象。DDD 实体强调的是业务身份和生命周期,两者不能只靠类名判断。

至于 DTO、VO、DO 和 Entity 在代码中怎么区分,后面的代码落地章节再单独整理,这里先不展开。

六、总结

实体和值对象看起来只是两个名词,实际决定的是我们如何表达业务。

  • 实体看身份,即使属性变化,仍然可以通过唯一标识持续跟踪;
  • 值对象看属性,属性相同就表示相同的业务含义;
  • 值对象尽量保持不可变,变化时创建新对象整体替换;
  • 不要用数据库主键、是否建表或者类名代替业务判断;
  • 同一个概念在不同限界上下文中,可能有不同的建模结果。

下一篇文章我们继续学习聚合和聚合根,看看实体和值对象应该如何组织在一起,以及一致性边界到底应该怎么划分。

相关推荐
Database_Cool_2 小时前
阿里云 RDS MySQL vs 腾讯云 CDB 国内云关系型数据库深度对比
数据库·mysql·阿里云
xiaohaiAIgeo2 小时前
【2026年】基于三维建模的实验室智慧管理平台:暖通能源照明的数字孪生方案
大数据·数据库·人工智能·科普知识
深盾科技_Virbox3 小时前
软件授权工具静默安装实践
java·运维·数据库·安全·软件需求
延凡科技3 小时前
延凡科技综合监控预警处置平台—— 一体化视频AI安防闭环系统设计与功能实现[特殊字符]️
数据库·人工智能·科技·安全·能源
老马聊技术3 小时前
Rocky Linux 9.0 安装 MySQL8.x详细教程
linux·数据库
醇氧3 小时前
主流 Agent 开发框架全解析(2026 最新)
大数据·数据库·人工智能·开源
吴声子夜歌4 小时前
Redis 6.x——整合SpringBoot
数据库·spring boot·redis
ATA88884 小时前
AI辅助生成SQL实战从连接配置到执行计划优化的完整技术流程
数据库·人工智能·智能问数