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

基于Spring实现DDD时的字段校验位置及错误返回方案咨询

DDD架构下的校验分层实践与错误返回方案

一、校验逻辑的分层原则

你提到的两种校验放置方案并不冲突,符合DDD规范的分层校验规则如下:

  • 适配层(DTO层)做前置契约校验:非业务强相关的格式校验、基础规则校验(比如字段非空、数字范围、日期格式等)可以放在DTO层,通过@Valid注解触发,这层校验属于接入层的合法性过滤,作用是快速拦截非法请求,避免无效请求打入核心领域层,完全符合DDD的设计规范。
  • 领域层做核心业务规则校验:和业务强绑定的规则(比如下单数量不能超过用户限购额度、商品必须处于可售状态等)必须放在领域层,要么在领域实体的构造/工厂方法中直接校验抛出异常,要么在领域服务中统一校验,保证不管请求来自REST接口、MQ消费还是内部服务调用,都必须经过核心业务规则校验,避免规则漏判。

你举例的「订单数量必须大于0」属于基础业务规则,可以两层都加校验:适配层做快速拦截返回,领域层做兜底校验,保证规则的全局一致性。


二、现有实现的优化点

你当前的实现逻辑是通顺的,但存在两个可优化的问题:

  1. 每次校验都调用Validation.buildDefaultValidatorFactory()是很重的操作,Spring已经自动配置了单例的Validator实例,直接注入即可,不需要重复创建。
  2. 把校验结果作为返回值交给控制器判断,会产生大量重复的样板代码,更推荐用「自定义异常+全局异常处理器」的方式实现,简化上层代码。

三、统一校验错误返回的最佳实践

步骤1:自定义领域校验异常

public class DomainValidationException extends RuntimeException {
    private final Set<ConstraintViolation<?>> violations;

    public DomainValidationException(Set<ConstraintViolation<?>> violations) {
        super("领域模型校验不通过");
        this.violations = violations;
    }

    public Set<ConstraintViolation<?>> getViolations() {
        return violations;
    }
}

步骤2:改造领域服务代码

@Service
public class OrderServiceImpl implements OrderService {
    // 直接注入Spring自动配置的Validator实例,无需自行创建
    private final Validator validator;
    private final OrderRepository orderRepository;

    // 构造函数注入
    public OrderServiceImpl(Validator validator, OrderRepository orderRepository) {
        this.validator = validator;
        this.orderRepository = orderRepository;
    }

    @Override
    public void save(Order order) {
        Set<ConstraintViolation<Order>> violations = validator.validate(order);
        if (!violations.isEmpty()) {
            throw new DomainValidationException(Collections.unmodifiableSet(violations));
        }
        orderRepository.save(order);
    }
}

步骤3:简化控制器代码

@RequestMapping(
    method = RequestMethod.POST,
    path = "/orders",
    consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> addOrder(@RequestBody @Valid OrderDTO orderDTO) {
    Order order = ... // 转换为领域对象
    orderService.save(order);
    return ResponseEntity.ok().build();
}

步骤4:新增全局异常处理器,统一返回JSON格式错误

@RestControllerAdvice
public class GlobalValidationExceptionHandler {

    // 处理DTO层@Valid触发的校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleDtoValidation(MethodArgumentNotValidException e) {
        Map<String, String> errors = new HashMap<>();
        e.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        return ResponseEntity.badRequest().body(errors);
    }

    // 处理领域层抛出的校验异常
    @ExceptionHandler(DomainValidationException.class)
    public ResponseEntity<Map<String, String>> handleDomainValidation(DomainValidationException e) {
        Map<String, String> errors = new HashMap<>();
        e.getViolations().forEach(violation -> 
            errors.put(violation.getPropertyPath().toString(), violation.getMessage())
        );
        return ResponseEntity.badRequest().body(errors);
    }
}

改造后校验错误会自动返回JSON格式的响应,示例如下:

{
  "quantity": "必须大于0"
}

内容的提问来源于stack exchange,提问作者Samson

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 05:45:04