DDD中实体基类只应声明身份契约与内聚验证,不可混入 createTime 等审计字段;ID 须不可变且统一抽象,验证逻辑内置;审计字段属基础设施层,应由 ORM 自动填充;复用行为优先用组合(如 ArchivalStatus 值对象)而非继承。

在 DDD 的 Java 实践中,抽象实体基类不是为了“统一加字段”,而是为了**声明领域实体共有的身份契约与生命周期语义**。直接在抽象类里塞 createTime、updateTime、version 等通用字段,看似省事,实则混淆了领域模型与基础设施关注点,容易导致业务含义被技术细节淹没。
抽象基类应只承载身份与内聚验证职责
一个符合 DDD 原则的实体基类,核心只做两件事:
-
声明唯一标识(Identity):提供统一的
id()方法签名,强制所有子类明确其身份来源(如 UUID、业务编码、组合键),不暴露 setter 或具体类型,避免 ID 被随意修改 -
支持内聚验证(Validate-on-change):继承自可验证基类(如
ValidatableBase),让实体能自主校验自身状态合法性(例如邮箱格式、金额非负),而非依赖外部服务
示例代码结构清晰体现意图:
public abstract class Entity {protected final String id;
protected Entity(String id) {
this.id = Objects.requireNonNull(id);
}
public String id() { return id; }
public abstract void validate();
}
通用审计字段属于持久化实现细节,不应污染领域层
createTime、lastModified 这类字段,本质是数据库行级元数据,服务于审计、乐观锁或查询排序等技术需求。它们:
立即学习“Java免费学习笔记(深入)”;
- 在领域模型中没有业务含义——用户下单时不会说“请带上创建时间”
- 由基础设施层(如 JPA 的
@CreatedDate、MyBatis 的拦截器)自动填充,无需领域对象感知 - 若强行塞进
Entity,会导致所有实体被迫承担与业务无关的责任,破坏单一职责
正确做法是:在数据访问层(Repository 实现类或 ORM 映射配置)中处理这些字段,领域实体保持纯粹。
需要复用行为?用组合,而不是继承通用字段
如果多个聚合确实需共享某些**有业务意义的生命周期行为**(例如“软删除”“归档状态流转”),应设计为独立的值对象或领域服务:
- 定义
ArchivalStatus值对象,封装isArchived()、archiveBy(User)等行为 - 在需要的聚合根中持有该值对象,通过组合获得能力,而非从基类继承一堆字段和空方法
- 这样既复用了逻辑,又保持了领域语义的清晰性——“订单可被归档”比“订单有 isDeleted 字段”更贴近业务语言
工厂负责初始化,而非基类硬编码默认值
有些团队会在抽象基类里写 initializeForNewCreation() 并设置默认时间。这仍是越界:初始化策略(是否设当前时间、谁来设、是否允许覆盖)属于具体聚合的创建上下文,应交给工厂(Factory)或构造函数处理。
例如:Order.of(customerId, items) 工厂内部决定是否调用 LocalDateTime.now(),而不是让所有实体都无差别地在基类里记下创建时间。



















