Введение в API проверки Vavr
1. обзор
Валидация - это часто встречающаяся задача в Java-приложениях, и поэтому много усилий было приложено для разработки валидационных библиотек.
Vavr (ранее известный как Javaslang) предоставляет полноценныйvalidation API. Это позволяет нам проверять данные простым способом, используя объектно-функциональный стиль программирования. Если вы хотите посмотреть, что предлагает эта библиотека из коробки, не стесняйтесь проверитьthis article.
В этом руководстве мы подробно рассмотрим API проверки библиотеки и узнаем, как использовать его наиболее подходящие методы.
2. ИнтерфейсValidation
Интерфейс проверки Vavr основан на концепции функционального программирования, известной какapplicative functor. Он выполняет последовательность функций во время накапливания результатов, даже если некоторые или все эти функции не выполняются во время цепочки выполнения.
Аппликативный функтор библиотеки построен на основе ее интерфейсаValidation. Этот интерфейс предоставляет методы для накопления ошибок валидации и проверенных данных, что позволяет обрабатывать их как пакет.
3. Проверка ввода пользователя
Проверка пользовательского ввода (например, данных, собранных с веб-слоя) выполняется плавно с использованием API проверки, поскольку сводится к созданию пользовательского класса проверки, который проверяет данные, в то же время накапливая возникающие ошибки, если таковые имеются.
Давайте проверим имя и адрес электронной почты пользователя, которые были отправлены через форму входа. Во-первых, нам нужно включитьVavr’s Maven artifact в файлpom.xml:
io.vavr
vavr
0.9.0
Затем давайте создадим доменный класс, моделирующий пользовательские объекты:
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, 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 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() иcombine().
4. Методыvalid(),invalid() иcombine()
Если предоставленные имя и адрес электронной почты соответствуют заданным регулярным выражениям, методvalidateField() вызываетvalid(). Этот метод возвращает экземплярValidation.Valid. И наоборот, если значения недопустимы, метод счетчикаinvalid() возвращает экземплярValidation.Invalid.
Этот простой механизм, основанный на создании разных экземпляровValidation в зависимости от результатов проверки, должен дать нам хотя бы базовое представление о том, как обрабатывать результаты (подробнее об этом в разделе 5).
Наиболее важным аспектом процесса проверки является методcombine(). Внутри этого метода используется классValidation.Builder, который позволяет комбинировать до 8 различных экземпляровValidation, которые могут быть вычислены разными методами:
static Builder combine(
Validation validation1, Validation validation2) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
return new Builder<>(validation1, validation2);
}
Самый простой классValidation.Builder принимает два экземпляра проверки:
final class Builder {
private Validation v1;
private Validation v2;
// standard constructors
public Validation, R> ap(Function2 f) {
return v2.ap(v1.ap(Validation.valid(f.curried())));
}
public Builder3 combine(
Validation v3) {
return new Builder3<>(v1, v2, v3);
}
}
Validation.Builder, вместе с методомap(Function) возвращает один единственный результат с результатами проверки. Если все результаты верны, методap(Function) отображает результаты в одно значение. Это значение сохраняется в экземпляреValid с помощью функции, указанной в его подписи.
В нашем примере, если указанные имя и адрес электронной почты действительны, создается новый объектUser. Конечно, можно сделать что-то совершенно другое с действительным результатом, то есть сохранить его в базе данных, отправить по электронной почте и так далее.
5. Обработка результатов проверки
Реализовать различные механизмы обработки результатов валидации довольно просто. Но как мы проверяем данные в первую очередь? В этой связи мы используем классUserValidator:
UserValidator userValidator = new UserValidator();
Validation, User> validation = userValidator
.validateUser("John", "[email protected]");
Как только экземплярValidation получен, мы можем использовать гибкость API проверки и обработать результаты несколькими способами.
Давайте подробнее остановимся на наиболее часто встречающихся подходах.
5.1. ЭкземплярыValid иInvalid
Этот подход является самым простым на сегодняшний день. Он состоит из проверки результатов валидации с экземплярами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", "[email protected]"),
instanceOf(Valid.class));
}
Вместо того, чтобы проверять достоверность результатов с помощью экземпляровValid иInvalid, мы должны просто пойти еще дальше и использовать методыisValid() иisInvalid().
5.2. APIisValid() и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", "[email protected]")
.isValid());
}
Экземпляр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));
}
И наоборот, если результаты верны, экземплярUser может быть получен с помощью методаget():
@Test
public void
givenValidUserParams_withGetMethod_thenGetUserInstance() {
assertThat(userValidator.validateUser("John", "[email protected]")
.get(), instanceOf(User.class));
}
Этот подход работает, как и ожидалось, но код все еще выглядит довольно многословным и длинным. Мы можем дополнительно сжать его, используя методtoEither().
5.3. APItoEither()
МетодtoEither() создает экземплярыLeft иRight интерфейсаEither. Этот дополнительный интерфейс имеет несколько удобных методов, которые можно использовать для сокращения обработки результатов проверки.
Если результаты верны, результат сохраняется в экземпляреRight. В нашем примере это будет действительный объектUser. И наоборот, если результаты недействительны, ошибки сохраняются в экземпляреLeft:
@Test
public void
givenValidUserParams_withtoEitherMethod_thenRightInstance() {
assertThat(userValidator.validateUser("John", "[email protected]")
.toEither(), instanceOf(Right.class));
}
Код теперь выглядит намного более кратким и оптимизированным. Но мы еще не закончили. ИнтерфейсValidation предоставляет методfold(), который применяет пользовательскую функцию, которая применяется к действительным результатам, а другую - к недопустимым.
5.4. APIfold()
Давайте посмотрим, как использовать методfold() для обработки результатов проверки:
@Test
public void
givenValidUserParams_withFoldMethod_thenEqualstoParamsLength() {
assertEquals(2, (int) userValidator.validateUser(" ", " ")
.fold(Seq::length, User::hashCode));
}
Использованиеfold() сокращает обработку результатов проверки до однострочности.
Стоит подчеркнуть, что возвращаемые типы функций, передаваемые в качестве аргументов методу, должны быть одинаковыми. Более того, функции должны поддерживаться параметрами типа, определенными в классе проверки, то естьSeq<String> иUser.
6. Заключение
В этой статье мы подробно изучили API проверки Vavr и узнали, как использовать некоторые из его наиболее подходящих методов. Чтобы увидеть полный список, проверьтеofficial docs API.
Контроль валидации Vavr представляет собой очень привлекательную альтернативу более традиционным реализациямJava Beans Validation,, таким какHibernate Validator.
Как обычно, все примеры, приведенные в статье, доступныover on GitHub.