累积验证冲突的设计模式



让我们想象一下,我们有一个进程,它接受以下类型的数据:

{"date":"2014-05-05", "url":"http://some.website.com","counter":3}
  • 此数据应正式验证:date的值应为可解析日期,url也应符合正常的 url 语法。
  • 此外,这些数据应该在逻辑上得到验证:date应该在将来,url应该是可访问的地址,返回200 OK

为了使其干净,必须将这两个验证例程分成不同的单元(类、实用程序等(。但是,所需的最终行为必须让用户清楚地了解数据中存在的所有违规行为。像这样:

{"Errors":[ "Specified date is not in the future",//Formal validation failed "Specified URL has invalid syntax"//Logical validation failed ]}

  • 我已经看到了所需行为的一些实现,但它们使用那些 利用Error对象,并且充满了检查,例如 Error.hasErrors()error==null,看起来并不优雅。
  • 我也看到了javax.validation的实施,它一次为您提供所有领域的所有违规行为。对于内容验证也可以实施相同的方法,但我不确定这是执行此操作的最佳方法。

问题:处理各种性质的多个异常/违规的最佳实践是什么?

UPD:答案的简短摘要:收集Violations,构建一个Exception,包含它们的上下文,原因和描述,使用拦截器进行渲染。请参阅答案中的参考链接:

http://beanvalidation.org/1.0/spec/JSR 303 规范

http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/validation.html 春豆验证

http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html Java EE 验证

用于验证的设计模式

为什么不使用异常作为常规控制流?

您可以执行以下操作:

定义一个抽象的 Check 类,如下所示:

public abstract class Check {
  private final List<Check> subChecks = new ArrayList<Check>();
  public Check add(Check subCheck) { subChecks.add(subCheck); return this }
  public void run(Data dataToInspect, List<Error> errors) {
    Error e = check(dataToInspect);
    if (e != null) {
       errors.add(e);
       return;
    }
    for (Check subCheck : subChecks) {
      subCheck.run(dataToInspect, errors);
    }
  }
  // Returns null if dataToInspect is OK.
  public abstract Error check(Data dataToInspect);
}

Data 是保存数据的类(需要检查(。可以是字符串,JSON对象,你有什么。

Error 表示在数据中检测到的问题,应大致如下所示:

public class Error {
  private String problem;
  public Error(String problem) { this.problem = problem }
  public String getProblem() { return problem }
  // maybe additional fields and method to better describe the detected problem...
}

然后,您就有了针对数据片段运行检查的代码:

public class Checker {
   private final List<Error> errors = new ArrayList<Error>();
   private final List<Check> checks = new ArrayList<Check>();
   public Checker() {
     checks.add(new DateIsParsableCheck().add(new DateIsInTheFurutreCheck());
     checks.add(new UrlIsWellFormed().add(new UrlIsAccessible());
     checks.add();
     ..
   }
   public void check(Data d) {
     for (Check c : checks) {
       Error e = c.run(d, errors);
       if (e != null) 
         errors.add(e);
     }
   }
}

稍微改变了我原来的答案。在当前的答案中,有子检查的概念:如果名为x的检查具有名为y的子检查,则仅当x检查成功时,y检查才会运行。例如,如果日期不可解析,则没有必要检查它是将来的。

在您的情况下,我认为所有/最合乎逻辑的检查应该是正式检查的子检查。

我认为没有最佳实践,因为这取决于您要实现的目标。在我看来,不应使用异常及其消息直接向用户显示。异常技术性太强,并且在很大程度上取决于抛出它们的上下文。

因此,我的方法是设计一个容器类型,该类型收集验证引发的所有异常。这些异常应保留尽可能多的上下文(不是以异常消息的形式,而是以传递到构造函数的字段的形式(。提供 getter 方法以使这些字段(属性(可访问。呈现视图时,您可以循环访问容器的所有条目,并生成适当的、人类可读的 i18n 消息。

这是@AlexandreSantos评论要求的一些伪代码。它没有经过打磨,也没有经过验证,只是我的初稿。所以不要指望优秀的设计。这只是一个如何实现/设计的示例:

public static void main(String[] args) {
    Violations violations = new Violations();
    Integer age = AgeValidator.parse("0042", "age", violations);
    URL url = UrlValidator.parse("http://some.website.com", "url", violations);
}
// Validator defining all the rules for a valid age value
public class AgeValidator {
    // Collection of validation rules for age values
    private static final Collection<Validator<String>> VALIDATORS = ...;
    // Pass in the value to validate, the name of the field
    // defining the value and the container to collect all
    // violations (could be a Map<String, ValidationException>)
    //
    // a return value of null indicates at least one rule violation
    public static Integer parse(String value, String name, Violations violations) {
        try {
            for (Validator<String> validator : VALIDATORS) validator.validate(value);
        } catch (ValidationException e) {
            violations.add(name, e);
        }
        return violations.existFor(name) ? null : Integer.parseInt(value);
    }
}

我之前在这里回答过这个问题

标记为好的答案是应用于验证的复合模式的示例(几乎(

当然,有大量的框架可以做到这一点。你可以做的一些聪明的事情,我已经用过了很好的效果,就是使用一个方面+一个验证器,或者确保整个新的和现有的代码被神奇地自动检查。

@Aspect
public class DtoValidator {
private Validator validator;
public DtoValidator() {
}
public DtoValidator(Validator validator) {
    this.validator = validator;
}
public void doValidation(JoinPoint jp){
    for( Object arg : jp.getArgs() ){
        if (arg != null) {
            Set<ConstraintViolation<Object>> violations = validator.validate(arg);
            if( violations.size() > 0 ){
                throw buildError(violations);
            }
        }
    }
}
private static BadRequestException buildError( Set<ConstraintViolation<Object>> violations ){
    Map<String, String> errorMap = new HashMap<String, String>();
    for( ConstraintViolation error : violations ){
        errorMap.put(error.getPropertyPath().toString(), error.getMessage());
    }
    return new BadRequestException(errorMap);
}
}

这是豆子配置的片段

<aop:config proxy-target-class="true">
    <aop:aspect id="dtoValidator" ref="dtoValidator" order="10">
        <aop:before method="doValidation"
                    pointcut="execution(public * com.mycompany.ws.controllers.bs.*.*(..))"/>
    </aop:aspect>
</aop:config>

现在,所有控制器方法都将在此处和将来应用该验证代码。

使用异常设计它会起作用,但你必须编写一个完整的框架来处理异常,其中许多异常拦截器无法处理。如果您感到编码之痒,那就去做吧。我的建议是有不同类别的例外。其中一些将是关键的例外,有些只是警告......你明白了。

你可以(我希望你这样做(使用一个经过验证的框架,可以很好地处理这个问题。我说的是JSR 303和Bean Validation through Spring:http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/validation.html

习惯需要一段时间,但它会给你 1000 倍的回报。

我只会传递所有错误的列表。列表中的项目可能不仅仅是异常,而是一些对象包装了有关错误的更多信息,例如错误参数的名称、其错误值、错误在字符串中的位置、验证类型(形式、ligical(、用于本地化向用户显示的错误消息的 ID...处理路径上的每个方法都可以追加到列表中。

最新更新