Перейти к основному содержимому

Введение в API проверки Vavr

· 6 мин. чтения

1. Обзор

Проверка — часто встречающаяся задача в Java-приложениях, поэтому в разработку библиотек проверки было вложено много усилий.

Vavr (ранее известный как Javaslang) предоставляет полноценный API проверки . Это позволяет нам проверять данные простым способом, используя объектно-функциональный стиль программирования. Если вы хотите взглянуть на то, что эта библиотека предлагает из коробки, не стесняйтесь проверить эту статью .

В этом руководстве мы подробно рассмотрим API проверки библиотеки и узнаем, как использовать его наиболее подходящие методы.

2. Интерфейс проверки

Интерфейс проверки Vavr основан на концепции функционального программирования, известной как аппликативный функтор . Он выполняет последовательность функций, накапливая результаты, даже если некоторые или все эти функции завершатся сбоем во время цепочки выполнения.

Аппликативный функтор библиотеки основан на реализации интерфейса Validation . Этот интерфейс предоставляет методы для накопления ошибок проверки и проверенных данных, что позволяет обрабатывать их как пакет.

3. Проверка пользовательского ввода

Проверка пользовательского ввода (например, данных, собранных с веб-уровня) осуществляется гладко с использованием API проверки, поскольку она сводится к созданию пользовательского класса проверки, который проверяет данные, накапливая возникающие ошибки, если таковые имеются.

Давайте проверим имя пользователя и адрес электронной почты, которые были отправлены через форму входа. Во- первых, нам нужно включить артефакт Vavr Maven в файл pom.xml :

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

Далее давайте создадим доменный класс, который моделирует пользовательские объекты:

public class User {
private String name;
private String email;

// standard constructors, setters and getters, toString
}

Наконец, давайте определим наш пользовательский валидатор:

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));
}
}

Класс UserValidator проверяет указанные имя и адрес электронной почты по отдельности с помощью метода validateField() . В этом случае этот метод выполняет типичное сопоставление шаблонов на основе регулярных выражений.

Суть в этом примере заключается в использовании методов valid() , invalid() и comb() .

4. Методы valid(), invalid() и comb()

Если предоставленное имя и адрес электронной почты соответствуют заданным регулярным выражениям, метод validateField() вызывает valid() . Этот метод возвращает экземпляр Validation.Valid . И наоборот, если значения недействительны, противоположный метод invalid() возвращает экземпляр Validation.Invalid .

Этот простой механизм, основанный на создании разных экземпляров Validation в зависимости от результатов проверки, должен дать нам хотя бы общее представление о том, как обрабатывать результаты (подробнее об этом в разделе 5).

Наиболее важным аспектом процесса проверки является метод comb() . Внутри этот метод использует класс 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);
}

Самый простой класс 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 вместе с методом ap(Function) возвращает один единственный результат с результатами проверки. Если все результаты допустимы, метод ap(Function) сопоставляет результаты с одним значением. Это значение сохраняется в экземпляре Valid с помощью функции, указанной в его сигнатуре.

В нашем примере, если указанные имя и адрес электронной почты допустимы, создается новый объект User . Конечно, можно сделать что-то совершенно другое с достоверным результатом, т.е. спрятать его в базу данных, отправить по электронной почте и т.д.

5. Обработка результатов проверки

Довольно легко реализовать различные механизмы для обработки результатов проверки. Но как мы проверяем данные в первую очередь? Для этого мы используем класс UserValidator :

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

После получения экземпляра проверки мы можем использовать гибкость API проверки и обрабатывать результаты несколькими способами.

Остановимся подробнее на наиболее часто встречающихся подходах.

5.1. Действительные и недействительные экземпляры _

Этот подход на сегодняшний день является самым простым. Он состоит из проверки результатов проверки с экземплярами Valid и Invalid :

@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));
}

Вместо того, чтобы проверять достоверность результатов с экземплярами Valid и Invalid , мы должны сделать еще один шаг и использовать методы isValid() и isInvalid() .

5.2. API isValid() и isInvalid( )

Использование тандема isValid() / isInvalid() аналогично предыдущему подходу, с той разницей, что эти методы возвращают true или false в зависимости от результатов проверки:

@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());
}

Недопустимый экземпляр содержит все ошибки проверки. Их можно получить с помощью метода 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));
}

И наоборот, если результаты верны, экземпляр User можно получить с помощью метода get() :

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

Этот подход работает, как и ожидалось, но код по-прежнему выглядит довольно многословным и длинным. Мы можем дополнительно сжать его, используя метод toEither() .

5.3. API toEither( )

Метод toEither() создает экземпляры Left и Right интерфейса Both . Этот дополнительный интерфейс имеет несколько удобных методов, которые можно использовать для сокращения времени обработки результатов проверки.

Если результаты действительны, результат сохраняется в экземпляре Right . В нашем примере это будет допустимый объект User . И наоборот, если результаты недействительны, ошибки сохраняются в экземпляре Left :

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

Теперь код выглядит гораздо более лаконичным и упорядоченным. Но мы еще не закончили. Интерфейс Validation предоставляет метод fold() , который применяет пользовательскую функцию, которая применяется к действительным результатам, а другую — к недействительным.

5.4. API сгиба( )

Давайте посмотрим, как использовать метод fold() для обработки результатов проверки:

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

Использование fold() сокращает обработку результатов проверки до одной строки.

Стоит подчеркнуть, что возвращаемые типы функций, передаваемые в качестве аргументов метода, должны быть одинаковыми. Кроме того, функции должны поддерживаться параметрами типа, определенными в классе проверки, т . е. Seq<String> и User .

6. Заключение

В этой статье мы подробно изучили API проверки Vavr и узнали, как использовать некоторые из его наиболее важных методов. Полный список см . в API официальной документации .

Управление проверкой Vavr обеспечивает очень привлекательную альтернативу более традиционным реализациям проверки Java Beans, таким как Hibernate Validator .

Как обычно, все приведенные в статье примеры доступны на GitHub .