DTO解析器应自行校验还是复用领域校验?是否需包装重抛领域异常?
解析器应复用领域层校验还是重复实现?
背景代码
值对象 Latitude
public class Latitude { private Double value; public Latitude(Double value) { if (value < -180 || value > 180) { throw RuntimeException("Invalid value for latitude"); } this.value = value; } public Double getValue() { return this.value; } }
DTO LatitudeDTO
public class LatitudeDTO { private Double value; public Double getValue() { return this.value; } }
目标用法
希望在应用服务中通过Parser实现DTO到值对象的转换:
Latitude latitude = latitudeParser.fromDTO(latitudeDTO);
两种解析器方案
方案1:复用领域层校验
public class LatitudeParser { public Latitude fromDTO(LatitudeDTO latitudeDTO) throws DtoParserException { try { return new Latitude(latitudeDTO.getValue()); } catch(RuntimeException e) { throw new DtoParserException(e); } } }
方案2:重复实现校验逻辑
public class LatitudeParser { public Latitude fromDTO(LatitudeDTO latitudeDTO) throws DtoParserException { Double value = latitudeDTO.getValue(); if (value < -180 || value > 180) { throw new DtoParserException("Invalid value for latitude"); } return new Latitude(value); } }
注:两种方案中,DtoParserException最终都会在表示层映射为400 Bad Request。
核心疑惑
我倾向于方案1,因为它避免了校验规则重复,但有观点认为:传给领域层的数据需要提前校验,领域层抛出的异常属于应用层bug,不应该用于告知用户输入无效。这让我纠结:解析器到底应该自行实现校验,还是复用领域层的校验逻辑?重复校验显然不合理,但又担心违反所谓的“领域层异常规范”。
解答
优先选择方案1,同时优化领域层的异常设计,这是兼顾维护性和语义正确性的最优解。
1. 重复校验是绝对的反模式
同一校验规则维护两次,后续规则变更(比如调整纬度范围)时,极容易出现漏改,直接增加系统的维护成本和出错概率,完全不符合DRY(Don't Repeat Yourself)原则。
2. 消除“领域层异常是应用层bug”的误解
这个观点的核心问题在于领域层使用了通用的RuntimeException——这类异常通常被默认归类为不可恢复的代码/系统bug,但如果把它替换为自定义的业务异常,就能清晰区分“业务规则校验失败”和“应用层bug”:
优化步骤:
- 定义领域层自定义业务异常:
public class InvalidLatitudeException extends Exception { public InvalidLatitudeException(String message) { super(message); } }
- 修改
Latitude构造函数,抛出自定义异常:
public class Latitude { private Double value; public Latitude(Double value) throws InvalidLatitudeException { if (value < -180 || value > 180) { throw new InvalidLatitudeException("Invalid value for latitude"); } this.value = value; } public Double getValue() { return this.value; } }
- 调整Parser实现(方案1的优化版):
public class LatitudeParser { public Latitude fromDTO(LatitudeDTO latitudeDTO) throws DtoParserException { try { return new Latitude(latitudeDTO.getValue()); } catch(InvalidLatitudeException e) { throw new DtoParserException(e.getMessage(), e); } } }
3. 退而求其次的选择
如果暂时无法修改领域层的异常设计,方案1依然是更优选项——哪怕捕获RuntimeException,只要在转译时明确标注这是用户输入触发的业务规则失败,就可以正常映射为400 Bad Request。相比重复校验带来的维护隐患,这种“异常语义不够精准”的问题完全可以接受。
内容的提问来源于stack exchange,提问作者Douglas Monteiro
相关产品推荐
相关产品推荐

