Introduction to Vavr’s Validation API – Vavr’的验证API简介

最后修改: 2017年 9月 3日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Validation is a frequently occurring task in Java applications, and hence a lot of effort has been put into the development of validation libraries.

验证是Java应用程序中经常出现的一项任务,因此人们在开发验证库方面投入了大量的精力。

Vavr (formerly known as Javaslang) provides a full-fledged validation API. It allows us to validate data in a straightforward manner, by using an object-functional programming style. If you want to peek at what this library offers out of the box, feel free to check this article.

Vavr(以前称为Javaslang)提供了一个全面的验证API。它允许我们通过使用对象功能的编程风格,以直接的方式验证数据。如果您想偷看一下这个库所提供的内容,请随时查看这篇文章

In this tutorial, we take an in-depth look at the library’s validation API and learn how to use its most relevant methods.

在本教程中,我们将深入了解该库的验证API,并学习如何使用其最相关的方法。

2. The Validation Interface

2.验证界面

Vavr’s validation interface is based on a functional programming concept known as an applicative functor. It executes a sequence of functions while accumulating the results, even if some or all of these functions fail during the execution chain.

Vavr的验证接口是基于一个函数式编程的概念,即应用性趣素。它执行一连串的函数,同时积累结果,即使这些函数中的某些或全部在执行链中失败。

The library’s applicative functor is built upon the implementers of its Validation interface. This interface provides methods for accumulating validation errors and validated data, therefore allowing to process both of them as a batch.

该库的应用性向量是建立在其Validation接口的实现者之上的。这个接口提供了积累验证错误和验证数据的方法,因此允许将它们作为一个批处理。

3. Validating User Input

3.验证用户输入

Validating user input (e.g., data collected from a web layer) is smooth using the validation API, as it boils down to creating a custom validation class that validates the data while accumulating resulting errors if any.

使用验证API,验证用户输入(例如,从网络层收集的数据)是很顺利的,因为它可以归结为创建一个自定义的验证类,验证数据,同时积累结果错误(如果有)。

Let’s validate a user’s name and email, which have been submitted via a login form. First, we need to include Vavr’s Maven artifact into the pom.xml file:

让我们来验证一下通过登录表格提交的用户姓名和电子邮件。首先,我们需要将Vavr的Maven工件纳入pom.xml文件。

<dependency>
    <groupId>io.vavr</groupId>
    <artifactId>vavr</artifactId>
    <version>0.9.0</version>
</dependency>

Next, let’s create a domain class that models user objects:

接下来,让我们创建一个为用户对象建模的领域类。

public class User {
    private String name;
    private String email;
    
    // standard constructors, setters and getters, toString
}

Finally, let’s define our custom validator:

最后,让我们来定义我们的自定义验证器。

public class UserValidator {
    private static final String NAME_PATTERN = ...
    private static final String NAME_ERROR = ...
    private static final String EMAIL_PATTERN = ...
    private static final String EMAIL_ERROR = ...
	
    public Validation<Seq<String>, User> validateUser(
      String name, String email) {
        return Validation
          .combine(
            validateField(name, NAME_PATTERN, NAME_ERROR),
            validateField(email, EMAIL_PATTERN, EMAIL_ERROR))
          .ap(User::new);
    }
	
    private Validation<String, String> validateField
      (String field, String pattern, String error) {
 
        return CharSeq.of(field)
          .replaceAll(pattern, "")
          .transform(seq -> seq.isEmpty() 
            ? Validation.valid(field) 
            : Validation.invalid(error));		
    }
}

The UserValidator class validates the supplied name and email individually with the validateField() method. In this case, this method performs a typical regular expression based pattern matching.

UserValidator类通过validateField()方法单独验证提供的姓名和电子邮件。在这种情况下,该方法执行了一个典型的基于正则表达式的模式匹配。

The essence in this example is the use of the valid() , invalid() and combine() methods.

这个例子的实质是使用valid()invalid()combine()方法。

4. The valid(), invalid() and combine() Methods

4.valid()、invalid()combine()方法

If the supplied name and email match the given regular expressions, the validateField() method calls valid() . This method returns an instance of Validation.Valid. Conversely, if the values are invalid, the counter-part invalid() method returns an instance of Validation.Invalid.

如果提供的名字和电子邮件符合给定的正则表达式,validateField()方法就会调用valid()。这个方法返回一个Validation.Valid的实例。相反,如果值是无效的,对应的invalid()方法返回一个Validation.Invalid实例。

This simple mechanism, based on creating different Validation instances depending on the validation results should give us at least a basic idea on how to process the results (more on this in section 5).

这个简单的机制,基于根据验证结果创建不同的验证实例,至少应该给我们一个如何处理结果的基本思路(更多内容见第5节)。

The most relevant facet of the validation process is the combine() method. Internally this method uses the Validation.Builder class, which allows to combine up to 8 different Validation instances that can be computed with different methods:

验证过程中最相关的方面是combine()方法。在内部,这个方法使用Validation.Builder类,它允许结合多达8个不同的Validation实例,可以用不同的方法进行计算。

static <E, T1, T2> Builder<E, T1, T2> combine(
  Validation<E, T1> validation1, Validation<E, T2> validation2) {
    Objects.requireNonNull(validation1, "validation1 is null");
    Objects.requireNonNull(validation2, "validation2 is null");
    return new Builder<>(validation1, validation2);
}

The simplest Validation.Builder class takes two validation instances:

最简单的Validation.Builder类需要两个验证实例。

final class Builder<E, T1, T2> {

    private Validation<E, T1> v1;
    private Validation<E, T2> v2;

    // standard constructors

    public <R> Validation<Seq<E>, R> ap(Function2<T1, T2, R> f) {
        return v2.ap(v1.ap(Validation.valid(f.curried())));
    }

    public <T3> Builder3<E, T1, T2, T3> combine(
      Validation<E, T3> v3) {
        return new Builder3<>(v1, v2, v3);
    }
}

Validation.Builder, along with the ap(Function) method, returns one single result with the validation results. If all results are valid, the ap(Function) method maps the results onto a single value. This value is stored in a Valid instance by using the function specified in its signature.

Validation.Builder,ap(Function)方法一起,返回一个带有验证结果的单一结果。如果所有的结果都有效,ap(Function)方法将结果映射到一个单一的值。这个值通过使用其签名中指定的函数被存储在Valid实例中。

In our example, if the supplied name and email are valid, a new User object is created. Of course, it is possible to do something entirely different with a valid result, i.e to stash it into a database, send it by email and so forth.

在我们的例子中,如果提供的姓名和电子邮件是有效的,就会创建一个新的User对象。当然,也可以用一个有效的结果做一些完全不同的事情,比如把它藏在数据库里,用电子邮件发送,等等。

5. Processing Validation Results

5.处理验证结果

It’s pretty easy to implement different mechanisms for processing validation results. But how do we validate data in the first place? To this extent, we use the UserValidator class:

实现不同的机制来处理验证结果是很容易的。但是我们首先要如何验证数据呢?在这种程度上,我们使用UserValidator类。

UserValidator userValidator = new UserValidator(); 
Validation<Seq<String>, User> validation = userValidator
  .validateUser("John", "john@domain.com");

Once an instance of Validation is obtained, we can leverage the flexibility of the validation API and process results in several ways.

一旦获得Validation的实例,我们就可以利用验证API的灵活性,以多种方式处理结果。

Let’s elaborate on the most commonly encountered approaches.

让我们详细介绍一下最常遇到的方法。

5.1. The Valid and Invalid Instances

5.1.有效无效的实例

This approach is the simplest one by far. It consists of checking validation results with the Valid and Invalid instances:

这种方法是迄今为止最简单的一种。它包括用ValidInvalid实例检查验证结果。

@Test
public void 
  givenInvalidUserParams_whenValidated_thenInvalidInstance() {
    assertThat(
      userValidator.validateUser(" ", "no-email"), 
      instanceOf(Invalid.class));
}
	
@Test
public void 
  givenValidUserParams_whenValidated_thenValidInstance() {
    assertThat(
      userValidator.validateUser("John", "john@domain.com"), 
      instanceOf(Valid.class));
}

Rather than checking the validity of results with the Valid and Invalid instances, we should just go one step further and use the isValid() and isInvalid() methods.

与其用ValidInvalid实例检查结果的有效性,我们不如更进一步,使用isValid()isInvalid()方法。

5.2. The isValid() and isInvalid() APIs

5.2.isValid()isInvalid() API

Using the tandem isValid() / isInvalid() is analogous to the previous approach, with the difference that these methods return true or false, depending on the validation results:

使用串联的isValid()/isInvalid()与之前的方法类似,不同的是这些方法根据验证结果返回truefalse

@Test
public void 
  givenInvalidUserParams_whenValidated_thenIsInvalidIsTrue() {
    assertTrue(userValidator
      .validateUser("John", "no-email")
      .isInvalid());
}

@Test
public void 
  givenValidUserParams_whenValidated_thenIsValidMethodIsTrue() {
    assertTrue(userValidator
      .validateUser("John", "john@domain.com")
      .isValid());
}

The Invalid instance contains all the validation errors. They can be fetched with the getError() method:

Invalid实例包含所有验证错误。它们可以通过getError()方法来获取。

@Test
public void 
  givenInValidUserParams_withGetErrorMethod_thenGetErrorMessages() {
    assertEquals(
      "Name contains invalid characters, Email must be a well-formed email address", 
      userValidator.validateUser("John", "no-email")
        .getError()
        .intersperse(", ")
        .fold("", String::concat));
 }

Conversely, if the results are valid, a User instance can be grabbed with the get() method:

反之,如果结果是有效的,可以用get()方法抓取一个User实例。

@Test
public void 
  givenValidUserParams_withGetMethod_thenGetUserInstance() {
    assertThat(userValidator.validateUser("John", "john@domain.com")
      .get(), instanceOf(User.class));
 }

This approach works as expected, but the code still looks pretty verbose and lengthy. We can compact it further using the toEither() method.

这种方法如期而至,但代码看起来仍然相当冗长。我们可以使用toEither()方法进一步压缩它。

5.3. The toEither() API

5.3.toEither() API

The toEither() method constructs Left and Right instances of the Either interface. This complementary interface has several convenience methods that can be used for shortening the processing of validation results.

toEither()方法构造了LeftRight接口的Either实例。这个补充接口有几个方便的方法,可以用来缩短验证结果的处理时间。

If the results are valid, the result is stored in the Right instance. In our example, this would amount to a valid User object. Conversely, if the results are invalid, the errors are stored in the Left instance:

如果结果是有效的,那么结果将被存储在Right实例中。在我们的例子中,这就相当于一个有效的User对象。反之,如果结果是无效的,那么错误就会存储在Left实例中。

@Test
public void 
  givenValidUserParams_withtoEitherMethod_thenRightInstance() {
    assertThat(userValidator.validateUser("John", "john@domain.com")
      .toEither(), instanceOf(Right.class));
}

The code now looks much more concise and streamlined. But we’re not done yet. The Validation interface provides the fold() method, which applies a custom function that applies to valid results and another one to invalid ones.

现在的代码看起来更加简洁和精简。但我们还没有完成。Validation接口提供了fold()方法,它应用一个自定义函数,适用于有效的结果,另一个适用于无效的结果。

5.4. The fold() API

5.4.fold() API

Let’s see how to use the fold() method for processing validation results:

让我们看看如何使用fold()方法来处理验证结果。

@Test
public void 
  givenValidUserParams_withFoldMethod_thenEqualstoParamsLength() {
    assertEquals(2, (int) userValidator.validateUser(" ", " ")
      .fold(Seq::length, User::hashCode));
}

The use of fold() reduces the processing of validation results to just a one-liner.

使用fold()可以将验证结果的处理减少到只需一个行代码。

It’s worth stressing that the functions’ return types passed as arguments to the method must be the same. Moreover, the functions must be supported by the type parameters defined in the validation class, i.e., Seq<String> and User.

值得强调的是,作为参数传递给方法的函数的返回类型必须是相同的。此外,这些函数必须被验证类中定义的类型参数所支持,即Seq<String>User

6. Conclusion

6.结论

In this article, we explored in depth Vavr’s validation API and learned how to use some of its most relevant methods. For a full list, check the official docs API.

在这篇文章中,我们深入探讨了Vavr的验证API,并学习了如何使用其最相关的一些方法。有关完整的列表,请查看官方文档API

Vavr’s validation control provides a very appealing alternative to more traditional implementations of Java Beans Validation, such as Hibernate Validator.

Vavr的验证控件为Java Beans验证的更传统的实现提供了一个非常有吸引力的选择,例如Hibernate验证器

As usual, all the examples shown in the article are available over on GitHub.

像往常一样,文章中展示的所有例子都可以在GitHub上找到