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 .