Vavrの検証APIの紹介

Vavrの検証APIの概要

1. 概要

検証はJavaアプリケーションで頻繁に発生するタスクであるため、検証ライブラリの開発に多くの努力が注がれています。

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

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

2. Validationインターフェース

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

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

3. ユーザー入力の検証

ユーザー入力(Webレイヤーから収集されたデータなど)の検証は、検証APIを使用するとスムーズになります。結果として、結果のエラーを蓄積しながらデータを検証するカスタム検証クラスを作成することになります。

ログインフォームから送信されたユーザーの名前とメールアドレスを確認しましょう。 まず、Vavr’s Maven artifactpom.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クラスは、2つの検証インスタンスを取ります。

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)メソッドは、検証結果とともに1つの結果を返します。 すべての結果が有効な場合、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. 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));
 }

逆に、結果が有効な場合は、get()メソッドを使用してUserインスタンスを取得できます。

@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つのライナーになります。

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

6. 結論

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

Vavrの検証コントロールは、Hibernate Validatorなどの従来のJava Beans Validation,の実装に代わる非常に魅力的な代替手段を提供します。

いつものように、記事に示されているすべての例は利用可能なover on GitHubです。