Quarkus Tip : Comment NE PAS créer une extension Quarkus

Quarkus Tip : Comment NE PAS créer une extension Quarkus

Quand on développe une application composée de plusieurs composants, il est fréquent de vouloir partager du code dans une librairie externe, par exemple via un JAR externe intégré comme une dépendance de vos composants.

Quarkus est un framework d’extension, chaque extension qu’il propose permet d’intégrer une technologie (client BDD, framework ORM, …) à Quarkus, pour qu’elle puisse être configurée globalement, utilisée facilement via CDI (injection de dépendances), fonctionner avec GraalVM, …

Quarkus a son propre système de build, via son plugin Maven ou Gradle, qui permet de déporter au build une partie du démarrage de votre application via une extension. Il serait donc naturel de partager le code commun à plusieurs composants développés avec Quarkus via une extension.

Hors, créer une extension est compliqué et nécessite des compétences poussées sur le fonctionnement interne de Quarkus. A part si vous avez besoin de fonctionnalités poussées telles que décrites dans le guide d’écriture des extensions, il est souvent suffisant de créer un JAR « standard » avec la technique décrite dans la suite de ce billet.

Nous allons donc créer un JAR standard via un module Maven tout ce qu’il y a de plus classique ; la seule spécificité est que pour que le code de ce JAR soit « découvert » par Quarkus, il faut qu’il soit indexé par Jandex. Jandex est utilisé par Quarkus pour découvrir le code de votre application au build via une indexation du code de votre application, au lieu de le découvrir au runtime via des mécanismes plus standard tel que le scan de classpath et la réflexion.

Quarkus va automatiquement indexer le code de votre application, mais pas de ses dépendances, on va donc ajouter le plugin Maven Jandex pour créer un index du code de votre JAR commun lors du packaging de celui-ci par Maven :

<build>
  <plugins>
    <plugin>
      <groupId>org.jboss.jandex</groupId>
      <artifactId>jandex-maven-plugin</artifactId>
      <version>1.2.1</version>
      <executions>
        <execution>
          <id>make-index</id>
          <goals>
            <goal>jandex</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Maintenant, nous allons pouvoir écrire du code qui sera découvert par Quarkus : par exemple, fournir des beans CDI que l’on pourra ensuite injecter dans notre code applicatif.

Prenons par exemple la création d’un client pour Consul basé sur le Consul Client for Java.

La première étape consiste à créer une classe représentant la configuration du client, pour cela, on va utiliser le principe du Config Mapping : une interface qui représente vos options de configurations :

@ConfigMapping(prefix = "consul")
interface ConsulConfig {
    String host();
    int port();
}

Cette classe peut avoir une configuration par défaut que se retrouve dans le fichier META-INF/microprofile.properties, ce fichier est automatiquement découvert par le système de configuration de Quarkus dans chaque JAR du classpath.
Vous pourrez ensuite surcharger cette configuration si nécessaire dans le fichier application.properties de votre application.

consul.host=localhost
consul.port=8500

On peut ensuite écrire un producer CDI qui se basera sur votre classe de configuration pour produire un client Consul comme un bean CDI injectable dans le code de votre application :

@ApplicationScoped // 1
public class ConsulClientProducer {
    @Inject ConsulConfig config; // 2

    @Produces // 3
    @ApplicationScoped // 4
    public Consul consulClient() {
        HostAndPort hostPort = HostAndPort.fromParts(config.host(), config.port());// 5
        return Consul.builder().withHostAndPort(hostPort).build(); // 6
    }
}
  1. Les producers CDI sont des beans CDI, ils doivent donc être déclarés via une annotation CDI.
  2. Le config mapping qui définit la configuration de notre client Consul est injecté dans notre producer.
  3. Les méthodes produisant des beans CDI doivent être annotées par @Produces.
  4. Ici on veut produire un bean de scope @ApplicationScoped.
  5. On crée la configuration du client Consul depuis le config mapping injecté dans notre producer.
  6. On crée le client Consul et on le retourne.

Une fois ce JAR packagé, il suffit de l’ajouter comme dépendance à notre application, puis de configurer les propriétés qui doivent l’être dans notre fichier application.properties (elle surchargeront celle du fichier microprofile.properties du JAR commun) :

consul.host=my.consul.host

Pour finir, nous pouvons injecter le bean du client Consul dans notre code application, ici un endpoint REST (JAX-RS) :

@Path("/")
public class HelloConsulEndpoint {
    @Inject Consul consulClient;

    @GET
    public String getFromConsul() {
        KeyValueClient kvClient = consulClient.keyValueClient();
        return kvClient.getValueAsString("foo").get(); 
    }
}

Ici, le client Consul est injecté dans notre endpoint JAX-RS, puis utilisé dans la méthode getFromConsul() pour récupérer la valeur d’une clé depuis Consul et la retourner à l’utilisateur.

J’en ai parlé en début d’article, une des fonctionnalité les plus classique fournie par une extension, est le support de la compilation native GraalVM. Même si on peut en faire une plus poussée via une extension, on peut quand même via un simple JAR commun fournir une liste de classe à enregistrer pour réflexion via l’annotation @RegisterForReflection.
Voici un exemple qui enregistrer 3 classes fictives pour réflexion (cette étape n’est pas nécessaire pour notre client Consul) :

@RegisterForReflection(targets={ FirstClass.class, SecondClass.class, FictiveClass})
public class NativeImageReflectiveConfiguration {
}

On peut aussi fournir directement les configurations GraalVM standard via les fichiers de configuration standard ou via des substitutions.

Et voilà comment ne pas créer une extension Quarkus.
Si vous insistez à vouloir en créer une, vous pouvez regarder mon talk sur fait à Devoxx FR 2021 sur le sujet 😉

4 réflexions sur « Quarkus Tip : Comment NE PAS créer une extension Quarkus »

  1. Dans le cas où l’on souhaite simplement produire des beans, y a t-il un avantage à créer une extension (qui produirait un AdditionalBeanBuildItem) vs créer une simple dépendance maven qui déclarerait le plugin jandex ?

    1. Non, pas d’avantage à mon avis, les techniques expliquées dans cet article suffisent.
      De plus, il sera toujours possible de transformer une librairie en extension plus tard si le besoin s’en fait sentir.

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.