Quarkus Tip: How NOT to create a Quarkus extension

Quarkus Tip: How NOT to create a Quarkus extension

When you develop an application composed of several components, it is frequent to want to share some code in an external library, for example via an external JAR integrated as a dependency of your components.

Quarkus is an extension framework, each extension it offers allows to integrate a technology (BDD client, ORM framework, …) to Quarkus, so it can be configured globally, used easily via CDI (dependency injection), work with GraalVM, …

Quarkus has its own build system, via its Maven or Gradle plugin, which allows to deport to build time the bootstarting of your application via an extension. So it would be a natural fit to share the common code to several components developed with Quarkus via an extension.

But creating an extension is complicated and requires advanced skills on the internal functioning of Quarkus. Except if you need advanced features as described in the extension writing guide, it is often enough to create a “standard” JAR with the technique described in the rest of this post.

So we will create a standard JAR via a classical Maven module; the only specificity is that for the code of this JAR to be “discovered” by Quarkus, it must be indexed by Jandex. Jandex is used by Quarkus to discover your application’s code at build time via indexing your application’s code, instead of discovering it at runtime via more standard mechanisms such as classpath scanning and reflection.

Quarkus will automatically index the code of our application, but not of its dependencies, so we will add the Maven Jandex plugin to create an index of the code of our common JAR when packaging it by 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>

Now, we will be able to write code that will be discovered by Quarkus: for example, provide CDI beans that we can then inject into our application code.

Let’s take for example the creation of a client for Consul based on the Consul Client for Java.

The first step is to create a class representing the client’s configuration, for this we will use the Config Mapping principle: an interface that represents your configuration items:

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

This class can have a default configuration that is found in the META-INF/microprofile.properties file, this file is automatically discovered by the Quarkus configuration system in each JAR of the classpath.
You can override this configuration if necessary in the application.properties file of your application.

consul.host=localhost
consul.port=8500

We can then write a CDI producer based on your configuration class to produce a Consul client as a CDI bean that can be injected into your application code:

@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. The CDI producers are CDI beans, so they must be declared via a CDI annotation.
  2. The config mapping that defines the configuration of our Consul client is injected into our producer.
  3. The methods producing CDI beans must be annotated with @Produces.
  4. Here we want to produce a bean of scope @ApplicationScoped.
  5. We create the Consul client configuration from the config mapping injected into our producer.
  6. We create the Consul client and return it.

Once this JAR is packaged, we just need to add it as a dependency to our application, and then set the properties that need to be set in our application.properties file (it will override the one in the microprofile.properties file from the common JAR) :

consul.host=my.consul.host

Finally, we can inject the Consul client bean into our application code, here a REST endpoint (JAX-RS):

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

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

Here, the Consul client is injected into our JAX-RS endpoint, and then used in the getFromConsul() method to retrieve a key value from Consul and return it to the user.

I mentioned it at the beginning of the article, one of the most classic features provided by an extension, is the support of the GraalVM native image compilation. Even if you can do a more advanced configuration via an extension, you can still do it via a simple common JAR providing a list of classes to register for reflection via the @RegisterForReflection annotation.
Here is an example that register 3 dummy classes for reflection (this step is not necessary for our Consul client):

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

One can also directly provide standard GraalVM configurations via standard configuration files or via substitutions.

And that’s how not to create a Quarkus extension.
If you insist on wanting to create one, you can watch my talk on done at Devoxx FR 2021 on the subject 😉 (french only).

4 thoughts on “Quarkus Tip: How NOT to create a Quarkus extension

  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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.