Introduction à l’API de validation de Vavr

Introduction à l’API de validation de Vavr

1. Vue d'ensemble

La validation étant une tâche fréquente dans les applications Java, des efforts considérables ont été déployés pour développer des bibliothèques de validation.

Vavr (anciennement Javaslang) fournit unvalidation API à part entière. Cela nous permet de valider les données de manière simple, en utilisant un style de programmation fonctionnant avec des objets. Si vous voulez jeter un œil à ce que cette bibliothèque offre hors de la boîte, n'hésitez pas à vérifierthis article.

Dans ce didacticiel, nous examinons en profondeur l'API de validation de la bibliothèque et apprenons à utiliser ses méthodes les plus pertinentes.

2. L'interfaceValidation

L’interface de validation de Vavr est basée sur un concept de programmation fonctionnelle appeléapplicative functor. Il exécute une séquence de fonctions tout en accumulant les résultats, même si certaines ou la totalité de ces fonctions échouent pendant la chaîne d'exécution.

Le foncteur applicatif de la bibliothèque est construit sur les implémenteurs de son interfaceValidation. Cette interface fournit des méthodes pour accumuler des erreurs de validation et des données validées, permettant ainsi de les traiter en lot.

3. Validation de l'entrée utilisateur

La validation des entrées utilisateur (par exemple, les données collectées à partir d'une couche Web) s'effectue sans heurts à l'aide de l'API de validation, car elle consiste à créer une classe de validation personnalisée qui valide les données tout en accumulant les erreurs éventuelles.

Validons le nom et l'adresse e-mail d'un utilisateur, qui ont été soumis via un formulaire de connexion. Tout d'abord, nous devons inclureVavr’s Maven artifact dans le fichierpom.xml:


    io.vavr
    vavr
    0.9.0

Ensuite, créons une classe de domaine qui modélise les objets utilisateur:

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

    // standard constructors, setters and getters, toString
}

Enfin, définissons notre validateur personnalisé:

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

La classeUserValidator valide le nom et l'e-mail fournis individuellement avec la méthodevalidateField(). Dans ce cas, cette méthode effectue une correspondance de modèle basée sur une expression régulière typique.

L'essence de cet exemple est l'utilisation des méthodesvalid(),invalid() etcombine().

4. Les méthodesvalid(),invalid() etcombine()

Si le nom et l'adresse e-mail fournis correspondent aux expressions régulières données, la méthodevalidateField() appellevalid(). Cette méthode renvoie une instance deValidation.Valid. Inversement, si les valeurs ne sont pas valides, la méthode du compteurinvalid() renvoie une instance deValidation.Invalid.

Ce mécanisme simple, basé sur la création de différentes instancesValidation en fonction des résultats de la validation, devrait nous donner au moins une idée de base sur la façon de traiter les résultats (plus à ce sujet dans la section 5).

La facette la plus pertinente du processus de validation est la méthodecombine(). En interne, cette méthode utilise la classeValidation.Builder, qui permet de combiner jusqu'à 8 instancesValidation différentes qui peuvent être calculées avec différentes méthodes:

static  Builder combine(
  Validation validation1, Validation validation2) {
    Objects.requireNonNull(validation1, "validation1 is null");
    Objects.requireNonNull(validation2, "validation2 is null");
    return new Builder<>(validation1, validation2);
}

La classe la plus simpleValidation.Builder prend deux instances de validation:

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, avec la méthodeap(Function), renvoie un seul résultat avec les résultats de la validation. Si tous les résultats sont valides, la méthodeap(Function) mappe les résultats sur une seule valeur. Cette valeur est stockée dans une instanceValid en utilisant la fonction spécifiée dans sa signature.

Dans notre exemple, si le nom et l'e-mail fournis sont valides, un nouvel objetUser est créé. Bien sûr, il est possible de faire quelque chose de tout à fait différent avec un résultat valide, c'est-à-dire de le stocker dans une base de données, de l'envoyer par courrier électronique, etc.

5. Traitement des résultats de validation

Il est assez facile de mettre en œuvre différents mécanismes de traitement des résultats de validation. Mais comment pouvons-nous valider les données en premier lieu? Dans cette mesure, nous utilisons la classeUserValidator:

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

Une fois qu'une instance deValidation est obtenue, nous pouvons tirer parti de la flexibilité de l'API de validation et traiter les résultats de plusieurs manières.

Expliquons les approches les plus courantes.

5.1. Les instancesValid etInvalid

Cette approche est de loin la plus simple. Il consiste à vérifier les résultats de validation avec les instancesValid etInvalid:

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

Plutôt que de vérifier la validité des résultats avec les instancesValid etInvalid, nous devrions simplement aller plus loin et utiliser les méthodesisValid() etisInvalid().

5.2. Les APIisValid() etisInvalid()

L'utilisation du tandemisValid() /isInvalid() est analogue à l'approche précédente, à la différence que ces méthodes renvoienttrue oufalse, selon les résultats de la validation:

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

L'instanceInvalid contient toutes les erreurs de validation. Ils peuvent être récupérés avec la méthodegetError():

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

Inversement, si les résultats sont valides, une instanceUser peut être récupérée avec la méthodeget():

@Test
public void
  givenValidUserParams_withGetMethod_thenGetUserInstance() {
    assertThat(userValidator.validateUser("John", "[email protected]")
      .get(), instanceOf(User.class));
 }

Cette approche fonctionne comme prévu, mais le code a quand même une apparence longue et prolixe. Nous pouvons le compacter davantage en utilisant la méthodetoEither().

5.3. L'APItoEither()

La méthodetoEither() construit les instancesLeft etRight de l'interfaceEither. Cette interface complémentaire a plusieurs méthodes pratiques qui peuvent être utilisées pour raccourcir le traitement des résultats de validation.

Si les résultats sont valides, le résultat est stocké dans l'instanceRight. Dans notre exemple, cela équivaudrait à un objetUser valide. A l'inverse, si les résultats ne sont pas valides, les erreurs sont stockées dans l'instanceLeft:

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

Le code semble maintenant beaucoup plus concis et simplifié. Mais nous n’avons pas encore terminé. L'interfaceValidation fournit la méthodefold(), qui applique une fonction personnalisée qui s'applique aux résultats valides et une autre aux résultats invalides.

5.4. L'APIfold()

Voyons comment utiliser la méthodefold() pour traiter les résultats de validation:

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

L'utilisation defold() réduit le traitement des résultats de validation à une seule ligne.

Il convient de souligner que les types de retour des fonctions passés comme arguments à la méthode doivent être identiques. De plus, les fonctions doivent être supportées par les paramètres de type définis dans la classe de validation, c'est-à-direSeq<String> etUser.

6. Conclusion

Dans cet article, nous avons exploré en profondeur l'API de validation de Vavr et appris à utiliser certaines de ses méthodes les plus pertinentes. Pour une liste complète, vérifiez lesofficial docs API.

Le contrôle de validation de Vavr offre une alternative très attrayante aux implémentations plus traditionnelles deJava Beans Validation, telles queHibernate Validator.

Comme d'habitude, tous les exemples présentés dans l'article sont disponiblesover on GitHub.