Dernière modification : Dec 08 , 2024

Prérequis

Avant de commencer cet exercice, assurez-vous d'avoir les éléments suivants installés sur votre système :

  • JDK 11 ou supérieur
  • Apache Maven
  • Un éditeur de texte ou une IDE Java (comme Eclipse, IntelliJ IDEA, ou Visual Studio Code)

Étapes

1. Création d'un nouveau projet Quarkus

Tout d'abord, créez un nouveau projet Quarkus en utilisant Maven avec la commande suivante :

mvn io.quarkus:quarkus-maven-plugin:2.7.4.Final:create \
    -DprojectGroupId=com.example \
    -DprojectArtifactId=quarkus-demo \
    -DclassName="com.example.HelloResource" \
    -Dpath="/hello"

Cette commande crée un nouveau projet Quarkus avec un endpoint REST basique /hello.

2. Exploration des extensions pour les APIs RESTful

Quarkus dispose de nombreuses extensions pour faciliter le développement des APIs RESTful. Pour voir la liste des extensions disponibles, vous pouvez utiliser la commande suivante :

./mvnw quarkus:list-extensions | grep rest

Explorez les extensions disponibles pour les fonctionnalités RESTful telles que la validation, la documentation Swagger, la sécurité, etc. Ajoutez les extensions pertinentes à votre projet en les spécifiant dans le fichier pom.xml.

3. Ajout d'extensions pour les technologies front-end

Pour intégrer des technologies front-end à votre projet Quarkus, vous pouvez explorer les extensions Web disponibles. Utilisez la commande suivante pour voir les extensions pertinentes :

./mvnw quarkus:list-extensions | grep web

Vous pouvez trouver des extensions pour des frameworks JavaScript populaires tels que React, Angular, Vue.js, ainsi que des bibliothèques comme jQuery. Ajoutez les extensions appropriées à votre projet pour intégrer la technologie front-end de votre choix.

Utilisation de l'extension Hibernate validator

Il est possible d'ajouter de la validation sur les paramètres en utilisaht Hibernate validator.

Quarkus utilise Hibernate Validator pour valider les entrées / sorties des services REST et des services métier à l'aide de la spécification de validation de Bean.

./mvnw quarkus:add-extension
  -Dextensions="io.quarkus:quarkus-hibernate-validator"

ou

quarkus extension add quarkus-hibernate-validator

Annoter des objets POJO avec des annotations de validateur telles que: @NotNull, @Digits, @NotBlank, @Min, @Max, …​ La liste des annotations de validation est disponible ici.

Nous pouvons ajouter des annotations sur la classe Monster.

public class Monster {
    private Integer id;
    private String monsterUUID;
    private String name;
    private String description;
    private Integer price;
    private Integer age;
    private String location;
}

Pour valider un objet, utilisez l'annotation @Valid:

public Response create(@Valid Monster sauce) {}

Dans notre cas, nous allons l'ajouter aux méthodes d'ajout et de mise à jour des monstres dans la classe AdoptionAdoptionRepository.

Avant :

/**
 * This interface describe the list of methods available to interact with the adoption system.
 */
public interface AdoptionRepository {

    Multi<Monster> getAllMonsters();
    Uni<Monster> addMonsterToAdopt(Monster monster);
    Uni<Monster> getMonsterByUuid(String id);
    Multi<Monster> searchMonstersByName(String name);
    void deleteMonsterById(String id);
    Uni<Monster> updateMonsterByUUID(String id, Monster monster);
}

Après :

/**
 * This interface describe the list of methods available to interact with the adoption system.
 */
public interface AdoptionRepository {

    Multi<Monster> getAllMonsters();
    Uni<Monster> addMonsterToAdopt(@Valid Monster monster);
    Uni<Monster> getMonsterByUuid(String id);
    Multi<Monster> searchMonstersByName(String name);
    void deleteMonsterById(String id);
    Uni<Monster> updateMonsterByUUID(String id, @Valid Monster monster);
}

Si une erreur de validation est déclenchée, un rapport de violation est généré et sérialisé au format JSON. Si vous souhaitez manipuler la sortie, vous devez intercepter dans le code l'exception ConstraintViolationException.

Créez Vos Contraintes Personnalisées

Nous pouvons également créer des annotations personalisées pour les contraintes de validation.

Dans notre cas, nous allons tester la validation des champs UUID.

Vous devez d'abord créer l'annotation personnalisée:

package com.byoskill.adoption.model;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import jakarta.validation.Constraint;

@Target({ ElementType.METHOD,
        ElementType.FIELD,
        ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR,
        ElementType.PARAMETER,
        ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = { UUIDValidator.class })
public @interface UUIDValid {

    String message() default "UUID is not valid";

    Class<?>[] groups() default {};

    Class<? extends String>[] payload() default {};

}

Vous devez implémenter la logique de validation dans une classe qui implémente ConstraintValidator.

package com.byoskill.adoption.model;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

public class UUIDValidator
        implements ConstraintValidator<UUIDValid, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext ctx) {
        if (value == null)
            return true;
        try {
            return java.util.UUID.fromString(value) != null && value.length() > 15;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }
}

Et l'utiliser normalement:

@UUIDValid
private String monsterUuid;

Manual Validation

Vous pouvez appeler le processus de validation manuellement au lieu de le transmettre à @Valid en injectant la classe Validator.

@Inject
Validator validator;

Et utilisez le ainsi :

String uuidValue = "123e4567-e89b-12d3-a456-426655440000";
Set<ConstraintViolation<String>> violations = validator.validate(uuidValue);

Il est alors possible de créer un test unitaire comme avec :


package com.byoskill.adoption.model;

import java.util.Set;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;

public class UUIDValidatorTest {

    public class Model {

        public Model(String uuid) {
            this.uuid = uuid;
        }

        @UUIDValid
        public String uuid;
    }

    @Test
    public void testUUIDValidation() {
        Assertions.assertEquals(0, validate("123e4567-e89b-12d3-a456-426655440000").size());
        Assertions.assertEquals(1, validate("").size());
    }

    private Set<ConstraintViolation<Model>> validate(String uuidValue) {
        ValidatorFactory validatorFactory = Validation.byDefaultProvider()
                .configure()
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        Set<ConstraintViolation<Model>> violations = validator.validate(new Model(uuidValue));
        return violations;
    }

}

Gestion des pannes

Quarkus fournit une extension pour améliorer la résilience et la tolérance aux pannes.

![Timeout](/materials/timeout.png)

Pour installer cette extension, il est nécessaire de taper la commande suivante :

quarkus extension add smallrye-fault-tolerance

Utilisation des fonctions de retry

La documentation de l'extension est ici.

Il est possible de rendre son code tolérant aux fautes en ajoutant l'annotation @Retry sur une ou plusieurs de vos ressources REST comme par exemple (ressources REST) :

@Retry(maxRetries = 4)

Utilisation de timeouts

Il est possible de créer un timeout au niveau de notre backend avec l'utilisation de l'annotation @Timeout.

Exemple

    @Produces(MediaType.APPLICATION_JSON)
    @GET
    @Retry(maxRetries =4)
    @Timeout(250)
    public MonsterView getAll() {
        return adoptionRepository.getAll();
    }

Utilisation des Fallbacks

Nous pouvons utiliser un Fallback , une route par défaut quand un endpoint ne fonctionne pas correctement.

Exemple avec la méthode getAll() dans le cas ou elle ne fonctionnerait pas correctement :


    @Inject
    Logger LOG;

    @Produces(MediaType.APPLICATION_JSON)
    @GET
    @Retry(maxRetries = 4)
    @Timeout(250)
    @Fallback(fallbackMethod = "getAllFallback")
    public MonsterView getAll() {
        return adoptionRepository.getAll();
    }

    public MonsterView getAllFallback() {
        LOG.info("Affichage du payload par défaut pour la liste des monstres");
        return new MonsterView(
                List.of(
                    new Monster("-1",
                            "Impossible de charger la liste des monstres",
                            "Affichage temporaire",
                            0,
                            "404.png")
                )
        );
    }

Utilisation d'un Circuit Breaker

Un disjoncteur est utile pour limiter le nombre de défaillances survenant dans le système lorsqu'une partie du système devient temporairement instable. Le disjoncteur enregistre les invocations réussies et échouées d'une méthode, et lorsque le ratio des invocations échouées atteint le seuil spécifié, le disjoncteur s'ouvre et bloque toutes les invocations ultérieures de cette méthode pendant une période donnée.

@CircuitBreaker(requestVolumeThreshold = 4)