Quarkus: Greener, Better, Faster, Stronger
Tags: java, quarkus, greener, backend
Published: 2023-12-22 16:23:22

So you heard of _**Quarkus**_ and how it's blazingly fast, and you want to take your share of the cake. 

You come from a world where sharing code is all about doing maven modules, and if you come from the _**Spring**_ world, scanning it when your application starts. Well, you can forget about jar scanning. If _**Quarkus**_ is faster and greener, it's -amongst other things- because it works at compile time as much as possible. And this is done through the extension mechanism.

So if you want to provide services to others and keep that fast frictionless feeling, you will have to do so.

In this article we will walk you through the scaffolding of an extension to a finished one, making use of it's best capabilities. I won't cover them all. Firstly because I don't know them all, secondly because it would take much more than an article...

I will try to keep a focus on speedy startups, and minimal memory consumption: the smaller, the better.

To do that, _**Quarkus**_ offers us a framework to do most of the job during the compilation/packaging phase, so that the application startup is left with the least possible tasks.

> 💡 You can follow along the article or deep dive directly in [the extension code](https://github.com/jtama/quarkus-extension-demo)

If _**Quarkus**_ claims to be greener, it is due to the fact that it does all it can to reduce memory footprint. Small artifact leads to a smaller classloader, that in turn leads less memory consumption.

## ⚠️ Warning

Writing an exension is not always the way. There are many things that can be done without the _burden_. There are even good articles on how to not write an extension ([here's one](https://www.loicmathieu.fr/wordpress/en/informatique/quarkus-tip-comment-ne-pas-creer-une-extension-quarkus/) from [Loïc Mathieu](https://twitter.com/loicmathieu))

## Context

Let's pretend that we are working for a big company (**Say Acme Company ©**) that has many Java applications and wants to migrate to _**Quarkus**_.

Our company has its own centralized tech conferences referential provider, and we want to facilitate its use to the developers. The preferred way to go with _**Quarkus**_ is to create a custom [`ConfigSourceProvider`](https://download.eclipse.org/microprofile/microprofile-config-2.0/apidocs/index.html?org/eclipse/microprofile/config/spi/ConfigSourceProvider.html).

So this is what we will be implementing here by putting the code in an extension so that is can be shareable with all the company applications, and can also provide some other services.

## 🚧 Scaffolding the extension

Creating an extension is pretty well documented in the [Quarkus guides](https://quarkus.io/guides/building-my-first-extension). 

So following the guide we will just do:

```bash
mvn io.quarkus.platform:quarkus-maven-plugin:3.6.1:create-extension -N \
    -DgroupId=org.acme \
    -DextensionId=configuration-provider
```
> 💡 Be sure to update this plugin version to the most recent one...

It will generate the following folder tree :

{% collapsible Project file folder%}

```bash
├── configuration-provider 1️⃣
│   ├── pom.xml 2️⃣
│   ├── deployment 3️⃣
│   ├── runtime 4️⃣
│   │   └── src
│   │       └── main
│   │           ├── java
│   │           └── resources
│   │               └── META-INF
│   │                   └── quarkus-extension.yaml 5️⃣
│   ├── integration-tests 6️⃣
```
1️⃣ - Root folder
2️⃣ - Parent maven project descriptor
3️⃣ - Compile time module, where all the magic will lie
4️⃣ - Runtime module, where our config source will be
5️⃣ - Extension descriptor
6️⃣ - Will contain our integration tests, consisting of an application using the extension and some test assertions

{% endcollapsible %}

In the above schema all folders are classic modules with a standard structure


## The runtime module

The runtime module contains plain _**Quarkus**_ flavoured `java` code, so we won't cover every detail but just highlight some interesting bits. 

Please keep in mind that the extension provides values by calling the _**Acme Referential**_ through its **REST API**. So it depends on a java rest client that needs to be configured, let's say that at rhe very least it needs a `URL`.

### The config source factory

I have chosen to follow the `ConfigSourceFactory` [path](https://quarkus.io/guides/config-extending-support#config-source-factory) to instantiate my `ConfigSource` bean. It gives me more possibilities as to how I can give context and instantiate the `ConfigSource`. I just don't need the registration part, as it will be done in the `deployment` module.

### Configuring the **REST API** client

External configuration mapping is done by the `EnvironmentRuntimeConfiguration` interface. 

```java
@ConfigRoot(phase = ConfigPhase.RUN_TIME) 1️⃣
@ConfigMapping(prefix = "acme")2️⃣
public interface EnvironmentRuntimeConfiguration {

    /**
     * The environment provider server URL.
     *
     * [NOTE]
     * ====
     * Value must be a valid `URI`
     * ====
     *
     * @asciidoclet 4️⃣
     */
    @WithName("environment.url") 3️⃣
    URI url();
```
1️⃣ - This says that this bean is used at runtime
2️⃣ - Specifies the root property key
3️⃣ - Overrides defaults property key with `acme.environment.url`. 
4️⃣ - Note the annotation that will be useful when generating the extension configuration.

([jump back to the end](#documenting))

### The AcmeConfigSource

The `AcmeConfigSource` is pretty straightforward: 

{% collapsible AcmeConfigSource open%}
```java
public class AcmeConfigSource implements ConfigSource {

    // Ignorable Code

    public AcmeConfigSource(EnvironmentRuntimeConfiguration runtimeConfiguration) {
        environmentProviderClient = new EnvironmentProviderClient(runtimeConfiguration.url());
        String pattern = "(?<env>.*)\\.(?<key>.*)";

        // Create a Pattern object
        patternMatcher = Pattern.compile(pattern);
    }

    @Override
    public String getValue(String propertyName) {
        if (Predicate.not(isAcme)
                .or(isProviderConfiguration)
                .test(propertyName))
            return null;

        // Now create matcher object.
        Matcher m = patternMatcher.matcher(propertyName);

        if (m.find()) {
            Map<String, String> env = environmentProviderClient.getEnvironment(m.group("env"));
            if (env != null) {
                return env.get(m.group("key"));
            }
        }
        return null;
    }
    // more mandatory code
}
```
{% endcollapsible %}

This is about all there is to say about the runtime module.

## The deployment module

Now let's dive into the deployment module. The code executed at compile time is usually placed in classes called processors.

Now I need to tell _**Quarkus**_ that it needs to execute code during the compile time. That is done by annotating methods with `@BuildStep`. Those methods produce AND consume `BuildItem`. There are [many of them](https://quarkus.io/guides/all-builditems), and one that will certainly suit your need.

There is no simple way to order multiple step methods, but _**Quarkus**_ will make sure the build items requested by a build step exist before invoking them, and that is how we can order buildsteps.

### Getting application scan result

In a class called `org.acme.configurationProvider.deploymen.EnvironmentInjectorProcessor`, I've created the following method: 

```java
@BuildStep
void askForApplicationScan(
   ApplicationIndexBuildItem index, 1️⃣
   BuildProducer<AcmeEnvironmentBuildItem> buildProducer 2️⃣) {
   index.getIndex().getAnnotations(ConfigProperty.class)
         .stream()
         .map(AnnotationInstance::values)
         .flatMap(List::stream)
         .filter(value -> value.asString().startsWith("acme"))
         .findFirst()
         .ifPresent(annotationInstance -> buildProducer.produce(new AcmeEnvironmentBuildItem()));
    }
```

1️⃣ - The method is asking to get an index of the classes present in the application that's using the extension
2️⃣ - Here it's requesting the build producer that is used to gather all produced `AcmeEnvironmentBuildItem`.

The index lets us interact with it through many entry points, and amongst them (method names are self-explanatory): `getClassesInPackage`, `getAnnotations`, `getAllKnownImplementors`, and many more.

In this case, I want to make sure there is a real need for this extension, and that means at least one appearance of a `ConfigProperty` with a key starting with `acme.`.

The `AcmeEnvironmentBuildItem` is an empty build item that I am using only to illustrate `BuildStep` ordering, also this is not very usual that an extension makes sure it's needed, but I want to highlight that it's possible.

I also created a second method: 

```java
@BuildStep
void envConfigSourceFactory(
   AcmeEnvironmentBuildItem acmeEnvironmentBuildItem, 1️⃣       
   BuildProducer<RunTimeConfigBuilderBuildItem> runTimeConfigBuilder 2️⃣) {
  if (acmeEnvironmentBuildItem != null) {
    runTimeConfigBuilder.produce(
      new RunTimeConfigBuilderBuildItem(
        AcmeConfigSourceFactoryBuilder.class.getName()
      )
    );
    return;
  }
  logger.warn("You shoud not use this extension if you don't need it.");
    }
```
1️⃣ - It consumes the `AcmeEnvironmentBuildItem`, that creates the build step _AcmeEnvironmentBuildItem_
2️⃣ - And it produces a `RunTimeConfigBuilderBuildItem`

We can see that the method only produces a `RunTimeConfigBuilderBuildItem` if needed. This build item provides a way to register our `AcmeConfigSourceFactoryBuilder` for _runtime_ phase.

If the given `AcmeEnvironmentBuildItem` is null, the buidstep will warn the user, asking them if they are sure the extension is needed, letting them know that they should probably remove the dependency.

When building their applications, the following log will be produced.

```bash
 WARN  [o.a.c.d.EnvironmentInjectorProcessor] 
    You shoud not use this extension if you don't need it.
```

### Testing the extension

To test the extension, I will create a simple application using two extensions (not using the [**_Quarkus_** `cli`](https://quarkus.io/guides/cli-tooling), since my extension isn't part of any platform): 

. `io.quarkus:picocli`
. `org.acme:configuration-provider`

And create the following class : 

```java
package org.acme.picocli;

import jakarta.inject.Inject;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;
import picocli.CommandLine;

@CommandLine.Command
public class Starter implements Runnable{

    @Inject
    Logger log;

    @ConfigProperty(name = "env.snowcamp.title")
    String snowcampConfTitle;
    @ConfigProperty(name = "env.snowcamp.author")
    String snowcampConfAuthor;

    @Override
    public void run() {
        log.info("********   WELCOME !     ********");
        log.infof("Welcome %s, that will present: \"%s\"%n",snowcampConfAuthor, snowcampConfTitle);
        log.info("*********************************");
    }
}
```

Now launching my application I get :


```bash
2023-12-13 08:30:48,502 ERROR [i.q.r.Application] 
  Failed to start application (with profile [dev]):
    java.lang.RuntimeException: Failed to start quarkus
    // Long stacktrace
   Caused by: io.smallrye.config.ConfigValidationException:
      Configuration validation failed:
    java.util.NoSuchElementException: 
      SRCFG00014: 
        The config property acme.environment.url is required but it could not be found in any config source
        // more stacktrace
```

Oh yes of course, I need to pass the acme provider `URL`, no issues... Except I don't have a dev environment provider at my disposal...

## The Dev Service

Here comes the Dev Service 🚀. 

Until now we have seen how _**Quarkus**_ can help us to reduce the number of beans and jar scanning at runtime by doing all those tasks at compile time. That will help us be _greener_ and _faster_, but not really _stronger_ from a developer experience perspective.

Dev Services supports the automatic provisioning of unconfigured third party services in development and test mode. They can be provided by extension leveraging (usually) [TestContainer library](https://testcontainers.com/).

So our extension will do just that.

### The configuration

As was the case for the runtime, I will use configuration classes, this time with the following:
 
```java 
@ConfigRoot(
   prefix = "acme", 1️⃣
   name = "", 2️⃣
   phase = ConfigPhase.BUILD_TIME 3️⃣)
```
1️⃣ - The properties key prefix
2️⃣ - By default extension properties are within the `quarkus` namespace (that would make `quarkus.acme.*`), and not being on of the core or [_**quarkiverse**_ extensions](https://github.com/quarkiverse), I decided to not use the default namespace
3️⃣ - Those properties will only be editable at compile time.

The configuration will contain two properties : 

<dl>
<dt>acme.devservices.enabled</dt>
<dd>Allows enabling/disabling the Dev Service for this extension. The default value is <code>true</code></dd>
<dt>acme.devservices.image-name</dt>
<dd>Allow to override the image used for this devservice. The default value is <code>quay.io/jtama/acme-provider</code></dd>
</dl>

### The Dev Dervice processor

I now need to make use of this configuration. So I create a `org.acme.configurationProvider.deployment.devservice.DevServicesProcessor` class. I will only show the most relevant part of the processor's code.

The class has two responsabilties: 
1. Create or retrieve a container and get the necessary values to access it.
2. Tell **__Quarkus__** that there is a running Dev Service, to configure the application accordingly.

All the code shown in this chapter will be a simplified version of the real extension for a better fit to this format.

#### Running the container

To run the container, we will leverage the `testcontainer` lib:

```java
Generic container = new GenericContainer("quay.io/jtama/acme-provider")
  .withNetwork(Network.SHARED) 1️⃣
  .withExposedPorts(8080); 2️⃣ 
container.start(); 3️⃣
return new DevServicesResultBuildItem.RunningDevService(DEV_SERVICE_LABEL,
                    container.getContainerId(), 4️⃣
                    container::close, 5️⃣
                    Map.of("acme.environment.url", "http://%s:%d".formatted(container.getHost(), container.getPort()))) 6️⃣;
```
1️⃣ - Use the Docker shared network
2️⃣ - Tells test container that this container listens on port 8080, and that it needs to be mapped.
3️⃣ - Start the container, and wait until it's ready
4️⃣ - Retrieve the container id
5️⃣ - Gives a closeable, that will allow for stopping the container when the application stops
6️⃣ - Provides a map of properties that will be used to wire up the application. 


In this sample you can see that testcontainer allows us to retrieve values that were dynamically generated when we started the container, such as the container host or exposed port.

Quarkus will then tie everything together auto-magically, so that your application is configured to use this Dev Service.

This sample code has been simplified to hell, the original class has 170+ lines of code, but that's enough to demonstrate how easy it is to provide a new Dev Service for an extension.

### Restarting the application

If I try to start my application again I get the following log : 

```bash
2023-12-22 09:13:46,579 INFO  [org.acm.con.dep.dev.DevServicesProcessor] (build-25) Dev Services for Acme Env started on http://localhost:49221
2023-12-22 09:13:46,582 INFO  [org.acm.con.dep.dev.DevServicesProcessor] (build-25) Other Quarkus applications in dev mode will find the instance automatically. For Quarkus applications in production mode, you can connect to this by starting your application with -Dacme.environment.url=http://localhost:49221
   ___                                    ___      _   _              
  / _ \_ __ ___  ___ _ __   ___ _ __     / __\ ___| |_| |_ ___ _ __   
 / /_\/ '__/ _ \/ _ \ '_ \ / _ \ '__|   /__\/// _ \ __| __/ _ \ '__|  
/ /_\\| | |  __/  __/ | | |  __/ |     / \/  \  __/ |_| ||  __/ |     
\____/|_|  \___|\___|_| |_|\___|_|     \_____/\___|\__|\__\___|_|     
                                                                      
   ___         _                  _                                   
  / __\_ _ ___| |_ ___ _ __   ___| |_ _ __ ___  _ __   __ _  ___ _ __ 
 / _\/ _` / __| __/ _ \ '__| / __| __| '__/ _ \| '_ \ / _` |/ _ \ '__|
/ / | (_| \__ \ ||  __/ |    \__ \ |_| | | (_) | | | | (_| |  __/ |   
\/   \__,_|___/\__\___|_|    |___/\__|_|  \___/|_| |_|\__, |\___|_|   
                                                      |___/           
                                              Powered by Quarkus 3.6.4
2023-12-22 09:13:47,319 INFO  [io.quarkus] (Quarkus Main Thread) configuration-provider-picocli-tests 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.6.4) started in 10.341s. 
2023-12-22 09:13:47,320 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2023-12-22 09:13:47,320 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, configuration-provider, picocli]
2023-12-22 09:13:47,398 INFO  [org.acm.con.it.Starter] (Quarkus Main Thread) ********   WELCOME !     ********
2023-12-22 09:13:47,398 INFO  [org.acm.con.it.Starter] (Quarkus Main Thread) Welcome j.tama, that will present: "Quarkus: Greener, Better, Faster, Stronger"

2023-12-22 09:13:47,398 INFO  [org.acm.con.it.Starter] (Quarkus Main Thread) *********************************
2023-12-22 09:13:47,404 INFO  [io.quarkus] (Quarkus Main Thread) configuration-provider-picocli-tests stopped in 0.005s
```

This means that both `acme.snowcamp.title`, `acme.snowcam.author` properties have been injected in the `Starter` command and used.

## Sliding down the wrong slope

Seeing how easy it was to give applications new capabilities I decided to go further. In our team, we have someone whose greatest battle is that speaking about _REST API's_ is heretic. He taught us how _REST_ can't be by its very nature an API. So I decided to help him in his fight, implementing a fault rectifier. I thus added a `ThisIsNotRestTransformerProcessor` in the deployment module. 

As I said, we want to focus on the `Greener and faster` mojo. So at all cost we want to keep : 

. Small artifacts, that will lead to smaller class loader, less memory consumption and quicker start-up times.
. Applications that only do what they really need, so, no useless extension dependencies.

To do that I will add the `quarkus-resteasy-reactive` extension to the runtime module with a special hint :

```xml
<dependency>
   <groupId>io.quarkus</groupId>
   <artifactId>quarkus-resteasy-reactive</artifactId>
   <optional>true</optional>
</dependency>
```

What that means, is that this extension won't be present in the final application artifact unless explicitly asked by the application.


### Adding/removing/modifying annotations

I will now add a new `@BuildStep` to the processor, but I want it to be triggered, only if the `@ResponseHeader` is present at runtime, meaning only if the application has added the `quarkus-resteasy-reactive` extension to its dependencies.

To do this I will first create a `java.util.function.BooleanSupplier`:

```java
public static class ReactiveResteasyEnabled 
   implements BooleanSupplier {
        
   @Override
   public boolean getAsBoolean() {
       return QuarkusClassLoader.
           isClassPresentAtRuntime( 1️⃣
              "org.jboss.resteasy.reactive.ResponseHeader");
        }
    }
```
1️⃣ Notice how _**Quarkus**_ helps me find out if a class will be present at runtime

All I have to do is use it:

```java
@BuildStep(onlyIf = ReactiveResteasyEnabled.class) 1️⃣
public void correctApproximations(
   ApplicationIndexBuildItem applicationIndexBuildItem,
   BuildProducer<AnnotationsTransformerBuildItem> transformers) {
   logger.infof("Correcting your approximations if any. We'll see at runtime !");
   transformers.produce(
       new AnnotationsTransformerBuildItem(
           new RestMethodCorrector()));
   return;
}
```
1️⃣ This build step will only execute if needed

The `AnnotationsTransformer` code is pretty straight forward: 

```java
private static class RestMethodCorrector 
      implements AnnotationsTransformer {

   public static final DotName RESPONSE_HEADER = 
      DotName.createSimple(
         org.jboss.resteasy.reactive.ResponseHeader.class);

   public static final AnnotationValue HEADER_NAME = 
      AnnotationValue.createStringValue(
         "name",
         "X-ApproximationCorrector");
   
   public static final AnnotationValue HEADER_VALUE = 
      AnnotationValue.createStringValue(
         "ignored",
         "It's more JSON over http really.");
        
    public static final AnnotationValue HEADER_VALUES = 
       AnnotationValue.createArrayValue(
          "value", 
          List.of(HEADER_VALUE));

   @Override
   public boolean appliesTo(AnnotationTarget.Kind kind) {
      return AnnotationTarget.Kind.METHOD == kind; 1️⃣
   }

   @Override
   public void transform(
      AnnotationsTransformer.TransformationContext context) {
      MethodInfo method = context.getTarget().asMethod();
      if (isRestEndpoint.test(method)) { 2️⃣
         Transformation transform = context.transform(); 3️⃣
         transform.add(RESPONSE_HEADER,HEADER_VALUES); 4️⃣
         transform.done(); 5️⃣
       }
    }
}
```
1️⃣ - Only applies transformations to method, because it's what `@ResponseHeader` targets
2️⃣ - If the given is an enpoint, i.e. is annotated with one of the following: `@GET`,`@PUT`,`@POST`,`@DELETE`,`@PATCH`
3️⃣ - Starts a new transformation
4️⃣ - Adds the `@ResponseHeader` to the method with correct values
5️⃣ - Ends the transformation

And we are done !

### Testing the newly added header

I will now create another simple application using: 

. `io.quarkus:quarkus-resteasy-reactive-jackson`
. `org.acme:configuration-provider`

And create the following enpoint: 

```java
package org.acme.enpoints;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.eclipse.microprofile.config.inject.ConfigProperty;

@Path("/acme")
public class AcmeResource {

    @ConfigProperty(name = "env.devoxxFR.title")
    String devoxxFRConfTitle;
    @ConfigProperty(name = "env.devoxxFR.author")
    String devoxxFRConfAuth;
    
    @GET
    public String hellodevoxxFR() {
        return "Welcome %s, that will present: \"%s\""
                .formatted(
                        devoxxFRConfAuth, 
                        devoxxFRConfTitle);
    }
}
```

If I now starts the application and trigger the endpoint with [httpie](https://httpie.io): 

```bash
> http localhost:8080/acme/foo

HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
X-ApproximationCorrector: It's more JSON over http really.
content-length: 91

Welcome Malvin le Martien, that will present: "Why does Elmyra Duff love animals so much ?"
```

\o/ That's a success ! My endpoint is magically augmented!

That may be a bit to much though. I think I will let the developers tell me if they want to be strict about this or not.

If they don't (and that will be the default behaviour), I will only log something at application startup. If they do want strictness, we will fallback to annotation transformation.

I will need to introduce one last feature for this.

### Recording bytecode for later invocation.

First of all, let's introduce a new property for the compile phase: `acme.strict.rest`. Indeed, the extension deployment code is run during the compilation phase, so if we want the developers to be able to pilot their behaviour we need early configuration. The property will default to `false`.

I then have to slightly modify my build step:

```java 
@BuildStep(onlyIf = ReactiveResteasyEnabled.class)
@Record(ExecutionTime.RUNTIME_INIT) 1️⃣
public void correctApproximations(
   AcmeConfigurationBuildTimeConfiguration compileConfiguration, 2️⃣
   ApplicationIndexBuildItem applicationIndexBuildItem,
   BuildProducer<AnnotationsTransformerBuildItem> transformers,
   ThisIsNotRestLogger thisIsNotRestLogger 2️⃣) {
   
   if (compileConfiguration.strict.isRestStrict) { 3️⃣
      logger.infof("Correcting your approximations if any. We'll see at runtime !");
      transformers.produce(new
        AnnotationsTransformerBuildItem(new
           RestMethodCorrector()));
      return;
    }
    Stream<MethodInfo> restEndpoints = applicationIndexBuildItem
       .getIndex()
       .getKnownClasses()
       .stream()
       .flatMap(
         classInfo -> classInfo.methods().stream())
       .filter(isRestEndpoint);
    thisIsNotRestLogger.youAreNotDoingREST(restEndpoints
                .map(this::getMessage)
                .toList()); 4️⃣
}

private String getMessage(MethodInfo methodInfo) {
   return 
      "You think you method \"%s#%s\" is doing rest but it's more JSON over HTTP actually."
      .formatted(
         methodInfo.declaringClass().toString(),
         methodInfo.toString());
}
```
1️⃣ - Tells _**Quarkus**_ this `@BuildStep` is doing byte code recording
2️⃣ - Injects configuration and recorder
3️⃣ - Add annotations only if in strict mode 
4️⃣ - Else invoke recorder with list of messages

And the recorder is a classical class : 

```java
package org.acme.configurationProvider.runtime;

import io.quarkus.runtime.annotations.Recorder;
import org.jboss.logging.Logger;

import java.util.List;

@Recorder 1️⃣
public class ThisIsNotRestLogger {

    private static final Logger logger = Logger.getLogger(ThisIsNotRestLogger.class);

    public void youAreNotDoingREST(List<String> warnings) {
        logger.errorf("%s******** You should Listen *********%s%s%s%s",
                System.lineSeparator(),
                System.lineSeparator(),
                String.join(System.lineSeparator(), warnings),
                System.lineSeparator(),
                "If I had been stricter I would have changed your code...");
    }

}
```
1️⃣ - Hey **_Quarkus_**, I'm a recorder !

Notice even though this method is invoked during build time, it will be invoked each time the application starts :

```bash 
   ___                                    ___      _   _              
  / _ \_ __ ___  ___ _ __   ___ _ __     / __\ ___| |_| |_ ___ _ __   
 / /_\/ '__/ _ \/ _ \ '_ \ / _ \ '__|   /__\/// _ \ __| __/ _ \ '__|  
/ /_\\| | |  __/  __/ | | |  __/ |     / \/  \  __/ |_| ||  __/ |     
\____/|_|  \___|\___|_| |_|\___|_|     \_____/\___|\__|\__\___|_|     
                                                                      
   ___         _                  _                                   
  / __\_ _ ___| |_ ___ _ __   ___| |_ _ __ ___  _ __   __ _  ___ _ __ 
 / _\/ _` / __| __/ _ \ '__| / __| __| '__/ _ \| '_ \ / _` |/ _ \ '__|
/ / | (_| \__ \ ||  __/ |    \__ \ |_| | | (_) | | | | (_| |  __/ |   
\/   \__,_|___/\__\___|_|    |___/\__|_|  \___/|_| |_|\__, |\___|_|   
                                                      |___/           
                                              Powered by Quarkus 3.6.4
2023-12-22 11:16:06,281 ERROR [org.acm.con.run.ThisIsNotRestLogger] (Quarkus Main Thread) 
******** You should Listen *********
You think you method "org.acme.configurationProvider.it.AcmeResource#java.lang.String hellodevoxxFR(java.lang.String event)" is doing rest but it's more JSON over HTTP actually.
You think you method "org.acme.configurationProvider.it.CustomResourceUtils#java.lang.String hello()" is doing rest but it's more JSON over HTTP actually.
If I had been stricter I would have changed your code... [Error Occurred After Shutdown]
```

If I now retrigger my endpoint, ,I get the following result : 

```bash
http localhost:8080/acme/foo
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
content-length: 91

Welcome Malvin le Martien, that will present: "Why does Elmyra Duff love animals so much ?"
```
The added header is no longer here.

## Documenting

Remember when I showed you [the `@asciidoclet` tag in the javadoc](#configuring-the-rest-api-client) ? 

Well scaffolding an extension also generates a `docs` module which leverages [Antora](https://antora.org/), and with minimal effort, we can produce a [nice and clean documentation](https://jtama.github.io/quarkus-extension-demo/acme-configuration-provider/main/index.html).

All the configuration documentation has been automatically generated from the javadoc

## Conclusion

I've only scratched the surface here, but I hope to have eased your path in writing an extension.

Please remember that extension maintainers are to be greatly accountable for keeping things small and fast.

Oh, and please don't mix things up on an extension, like I did. That was for demonstration only purpose, please do not try to reproduce this at home...

Happy coding !

## The source code

All the sources (and a bit more) I've shown in this article can be found in the following repository. Please feel free to clone/fork it and play with it !
{% embed https://github.com/jtama/quarkus-extension-demo %}. There you will find the finished extension, its integration tests, picocli usage, and the _acme centralized tech conference referential_.