You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 16:25:21