1. 概要

検証はJavaアプリケーションで頻繁に発生するタスクであるため、検証ライブラリの開発に多大な労力が費やされてきました。

Vavr (旧称Javaslang)は、本格的な検証APIを提供します。 これにより、オブジェクト関数型プログラミングスタイルを使用して、データを簡単な方法で検証できます。 このライブラリが提供するものをすぐに確認したい場合は、この記事をチェックしてください。

このチュートリアルでは、ライブラリの検証APIを詳しく調べ、最も関連性の高いメソッドの使用方法を学習します。

2. 検証インターフェース

Vavrの検証インターフェースは、アプリケーションファンクターとして知られる関数型プログラミングの概念に基づいています。 実行チェーン中にこれらの関数の一部またはすべてが失敗した場合でも、結果を蓄積しながら一連の関数を実行します。

ライブラリのアプリケーションファンクタは、Validationインターフェイスの実装者に基づいて構築されています。 このインターフェースは、検証エラーと検証済みデータを蓄積するためのメソッドを提供するため、両方をバッチとして処理できます。

3. ユーザー入力の検証

ユーザー入力(たとえば、Webレイヤーから収集されたデータ)の検証は、検証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()、および combine()メソッドの使用です。

4. valid()、 invalid()および combine()メソッド

指定された名前と電子メールが指定された正規表現と一致する場合、 validateField()メソッドは valid()を呼び出します。 このメソッドは、Validation.Validのインスタンスを返します。 逆に、値が無効な場合、カウンターパート invalid()メソッドはValidation.Invalidのインスタンスを返します。

検証結果に応じて異なるValidationインスタンスを作成することに基づくこの単純なメカニズムにより、結果の処理方法に関する少なくとも基本的なアイデアが得られるはずです(これについてはセクション5で詳しく説明します)。

検証プロセスの最も関連性の高い側面は、 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);
}

最も単純なValidation.Builderクラスは、2つの検証インスタンスを取ります。

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)メソッドとともに、検証結果とともに1つの結果を返します。 すべての結果が有効な場合、 ap(Function)メソッドは結果を単一の値にマップします。 この値は、署名で指定された関数を使用して、Validインスタンスに格納されます。

この例では、指定された名前と電子メールが有効な場合、新しいUserオブジェクトが作成されます。 もちろん、有効な結果でまったく異なることを行うことは可能です。つまり、データベースに格納したり、電子メールで送信したりすることなどです。

5. 検証結果の処理

検証結果を処理するためのさまざまなメカニズムを実装するのは非常に簡単です。 しかし、そもそもデータをどのように検証するのでしょうか。 この点で、UserValidatorクラスを使用します。

UserValidator userValidator = new UserValidator(); 
Validation<Seq<String>, User> validation = userValidator
  .validateUser("John", "[email protected]");

Validation のインスタンスが取得されると、検証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", "[email protected]"), 
      instanceOf(Valid.class));
}

ValidおよびInvalidインスタンスで結果の有効性を確認するのではなく、さらに一歩進んで isValid()およびisInvalidを使用する必要があります。 ()メソッド。

5.2. isValid()および isInvalid() API

タンデム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. toEither() API

toEither()メソッドは、EitherインターフェースのLeftおよびRightインスタンスを構築します。 この補完的なインターフェースには、検証結果の処理を短縮するために使用できるいくつかの便利な方法があります。

結果が有効な場合、結果はRightインスタンスに保存されます。 この例では、これは有効なUserオブジェクトになります。 逆に、結果が無効な場合、エラーはLeftインスタンスに保存されます。

@Test
public void 
  givenValidUserParams_withtoEitherMethod_thenRightInstance() {
    assertThat(userValidator.validateUser("John", "[email protected]")
      .toEither(), instanceOf(Right.class));
}

コードは、はるかに簡潔で合理化されたように見えます。 しかし、まだ終わっていません。 Validation インターフェイスは、 fold()メソッドを提供します。このメソッドは、有効な結果に適用されるカスタム関数と無効な結果に適用される別のカスタム関数を適用します。

5.4. fold() API

fold()メソッドを使用して検証結果を処理する方法を見てみましょう。

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

fold()を使用すると、検証結果の処理が1つのライナーになります。

メソッドに引数として渡される関数の戻り型は同じでなければならないことを強調する価値があります。 さらに、関数は、検証クラスで定義された型パラメーターによってサポートされている必要があります。 シーケンスユーザー

6. 結論

この記事では、Vavrの検証APIを詳しく調べ、最も関連性の高いメソッドのいくつかを使用する方法を学びました。 完全なリストについては、公式ドキュメントAPIを確認してください。

Vavrの検証コントロールは、 HibernateValidatorなどのJavaBeansValidationの従来の実装に代わる非常に魅力的な方法を提供します。

いつものように、記事に示されているすべての例は、GitHubから入手できます。