在 ddd 中,值对象应通过构造确保内在有效性;javax.validation 适用于外部输入校验阶段,而非嵌入值对象内部——验证应在创建时由外部协调器(如解析器、应用服务)执行,而非由值对象自身承担。
在 ddd 中,值对象应通过构造确保内在有效性;javax.validation 适用于外部输入校验阶段,而非嵌入值对象内部——验证应在创建时由外部协调器(如解析器、应用服务)执行,而非由值对象自身承担。
值对象(Value Object)的核心设计原则是不变性与自我验证:一旦创建,其状态必须合法且不可更改。因此,验证逻辑不应“延迟”到运行时通过反射校验字段,而应前置到构造过程中——即在 ofBytes() 等静态工厂方法内主动检查并抛出语义明确的异常。
以下是对原始 Size 类的重构建议(移除 @Min/@Positive 等声明式注解,改用防御性编程):
@Getter
@EqualsAndHashCode
@RequiredArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public final class Size {
private final long bytes;
public static Size ofBytes(long bytes) {
if (bytes < 1) {
throw new IllegalArgumentException("Size must be greater than zero: " + bytes);
}
return new Size(bytes);
}
public static Size ofKilobytes(long kilobytes) {
return ofBytes(kilobytes * 1024L); // 注意 long 溢出防护(见下文)
}
public static Size ofMegabytes(long megabytes) {
return ofBytes(megabytes * 1024L * 1024L);
}
}✅ 优势:
- 构造即校验,杜绝非法实例存在;
- 无运行时反射开销,类型安全;
- 异常语义清晰(IllegalArgumentException 表达业务约束违反);
- 符合 DDD “值对象必须始终有效”的建模契约。
⚠️ 关于 javax.validation 的正确定位:
它并非值对象的组成部分,而是面向外部输入的校验工具链一环。例如,在 Web 层接收 JSON 请求时:
@PostMapping("/documents")
public ResponseEntity<?> createDocument(@Valid @RequestBody DocumentRequest request) {
// 此处 @Valid 触发 javax.validation,校验 request 中的 size 字段(若为 DTO)
Size size = Size.ofBytes(request.getSizeInBytes()); // ✅ 安全校验后构造值对象
Document doc = Document.builder()
.name(Name.of(request.getName()))
.checksum(Checksum.of(request.getChecksum()))
.size(size)
.build();
return ResponseEntity.ok(repository.save(doc));
}? 关键注意事项:
立即学习“Java免费学习笔记(深入)”;
- 不要将 Validator 注入值对象:这违反了值对象的纯数据+行为内聚原则,也破坏了不可变性;
-
警惕数值溢出:ofKilobytes(1_000_000_000) 可能导致 long 溢出,建议在工厂方法中加入 Math.multiplyExact() 防护:
public static Size ofKilobytes(long kilobytes) { long bytes; try { bytes = Math.multiplyExact(kilobytes, 1024L); } catch (ArithmeticException e) { throw new IllegalArgumentException("Size overflow: " + kilobytes + " KB"); } return ofBytes(bytes); } - 区分信任边界:来自数据库或内部服务的 Size 实例默认可信(已通过领域规则保障),无需重复校验;仅对用户输入、API 请求、文件解析等外部边界输入启用 javax.validation 或自定义解析校验。
总结:值对象的验证是构造时的强制契约,而非运行时的可选检查。把校验逻辑下沉到工厂方法中,既符合 DDD 语义,又提升系统健壮性与可维护性。javax.validation 应作为基础设施层的辅助工具,服务于输入网关,而非污染领域模型。


















