
在ddd实践中,值对象的校验不应侵入其内部结构,而应在创建阶段(如解析外部输入时)由外部验证器统一执行,确保值对象保持不可变、无副作用且专注领域语义。
在ddd实践中,值对象的校验不应侵入其内部结构,而应在创建阶段(如解析外部输入时)由外部验证器统一执行,确保值对象保持不可变、无副作用且专注领域语义。
在领域驱动设计(DDD)中,Size 这类值对象(Value Object)的核心职责是表达领域概念、保证内在一致性与不可变性,而非承担校验逻辑的调度或执行。因此,将 Validator 实例作为字段注入到 Size 类中(如 private final Validator validator)是一种反模式:它破坏了值对象的纯数据语义,引入了基础设施耦合,并违背了“值对象应无行为依赖”的设计原则。
✅ 正确做法:校验发生在值对象构建之前或构建后立即进行,由上下文(如应用服务、API控制器或解析器)负责,而非值对象自身。
以下是一个符合DDD原则的实践示例:
// 值对象定义(纯净、无框架依赖)
@Getter
@EqualsAndHashCode
@RequiredArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public class Size {
@Min(value = 1, message = "Size must be greater than zero")
private final long bytes;
public static Size ofBytes(long bytes) {
return new Size(bytes);
}
public static Size ofKilobytes(long kilobytes) {
return new Size(kilobytes * 1024L);
}
public static Size ofMegabytes(long megabytes) {
return new Size(megabytes * 1024L * 1024L);
}
}? 注意:@Min 等注解仅作为元数据声明,不触发自动校验——它们需配合 JSR-303(javax.validation)运行时显式调用。
立即学习“Java免费学习笔记(深入)”;
在校验入口点(例如 REST API 层或命令解析器),使用标准验证器执行校验:
@Service
public class DocumentService {
private final Validator validator; // 通过 Spring 注入或静态工厂获取
public DocumentService() {
this.validator = Validation.buildDefaultValidatorFactory().getValidator();
}
public Document createDocument(String name, String checksum, long sizeInBytes) {
Size size = Size.ofBytes(sizeInBytes);
// ✅ 在可信边界处主动校验(如接收 HTTP 请求后)
Set<ConstraintViolation<Size>> violations = validator.validate(size);
if (!violations.isEmpty()) {
String errorMsg = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining("; "));
throw new IllegalArgumentException("Invalid Size: " + errorMsg);
}
return Document.builder()
.name(new Name(name))
.checksum(new Checksum(checksum))
.size(size)
.build();
}
}? 关键设计要点总结:
- 校验时机明确:仅对来自外部(如 HTTP、消息队列、文件导入)的未信任输入执行校验;从数据库加载或内部构造的值对象默认视为已验证(可考虑引入 TrustedSize/UntrustedSize 类型区分,强化编译期契约);
- 零侵入值对象:Size 不持有 Validator,不暴露 validate() 方法,不抛出校验异常——它只负责“存在即合法”;
- 分层职责清晰:应用层(Application Layer)或接口适配层(Interface Adapter)承担校验责任,体现“Parse, don’t validate”理念(先解析为领域类型,再集中验证);
- 性能与可测性兼顾:验证逻辑集中、可单元测试,且避免在高频调用路径(如 getter)中触发反射校验。
遵循这一模式,你的 Document 实体及其组合的值对象(Name、Checksum、Size)将真正成为轻量、稳定、可复用的领域基石,而非被框架细节拖累的“半领域半基础设施”混合体。



















