Manage your application properties properly with Quarkus and Microprofile Config

Manage your application properties properly with Quarkus and Microprofile Config

Web application development in the modern age is far more extensive than it was before. There is nothing surprising with this fact as technology moves forward, applications become more functional, and far more variables get introduced.

To avoid hardcoded variable definitions in particular classes, we need proper configuration files with properties. Popular web frameworks like Spring, Struts, etc. already resolved this by placing all configuration properties in .xml or .properties file. Mostly these properties are read by specific framework libraries.

But what if you need to introduce your own properties and want to manage them as easily as the framework does?
The best practice said: "Separate configuration from code!". This is the third principle of the 12-factor methodology.

Today you are going to learn how easy property management is done for developers in the Quarkus framework with help of MicroProfile Config.

Getting started

Setup your local environment to follow the guide. You will need:

First of all, Quarkus stores its properties in the application.properties file under the src/main/resources directory. It contains framework's related properties as well as user-defined properties. Let's define the next properties:

#application.properties file
quarkus.http.port=8081

myapp.title=Breakfast Pub
user.status=unknown
user.posts.min=1
user.posts.max=10
user.residence=El Dorado

There are a few ways how you can manage your own properties. Let's start with a simple one first.

Using the ConfigProperty annotation

Basically, you are inserting properties in an ad-hoc manner.
Underneath Quarkus is using MicroProfile libraries, so there is no need to explicitly define dependency in the pom.xml file.
For example, we have a REST endpoint where we want to insert our properties directly. All you need to do is to inject your property via ConfigProperty annotation, where you specify the property name and assign a variable to it.

Create ApplicationResource.java file under the src/main/java folder, with the following content:

import org.eclipse.microprofile.config.inject.ConfigProperty;

@Path("/myapp")
public class ApplicationResource {
    @ConfigProperty(name = "myapp.title")
    String applicationTitle;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String printAppTitle() {
        return applicationTitle; 
    } //curl http://localhost:8081/myapp -> Breakfast Pub
 }

Run your application by typing from the root of the project: mvn quarkus:dev
By accessing the defined REST endpoint at localhost:8081/myapp you will see the defined property in the output.

The benefit of this approach is simplicity. Most of the time it's useful when you need to insert a specific property that doesn't logically fit in any group of properties (let's call it a group orphan property).
However, if you need to define more properties and use those in multiple locations, with this approach you will end up wasting half of the file just for properties definition.

Using the ConfigPropeties annotation with class

This is a far more flexible approach and it allows you to group related properties without much work.

Create UserConfiguration.java file under the src/main/java/config folder, with the following content:

import io.quarkus.arc.config.ConfigProperties;

@ConfigProperties(prefix = "user")
public class UserConfiguration {
    public String status;  //wired with user.status property
    public UserPosts posts;

    public static class UserPosts {
        public String min;   //wired with user.posts.min property
        public String max;   //wired with user.posts.max property
    }
}

As you may notice Quarkus use annotations for properties configuration from different packages, those are:

  • import org.eclipse.microprofile.config.inject.ConfigProperty;
  • import io.quarkus.arc.config.ConfigProperties;
    By the time of writing this post, the version of Quarkus is 1.13.4.Final. Later versions will depend on the most recent MicroProfile Config 2.0 where the annotation for group(bulk) properties org.eclipse.microprofile.config.inject.ConfigProperties will be included.

The above class contains a static nested class as well. By creating nested classes you respectively access defined nested properties. The class names can be arbitrary.

Your ApplicationResource.java file will be rewritten with:

@Path("/myapp")
public class ApplicationResource {
    @Inject UserConfiguration userConfiguration;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String print() {
        return "Status: " + userConfiguration.status
                 + " Minimum allowed posts: " + userConfiguration.posts.min
                 + " Maximum allowed posts: " + userConfiguration.posts.max; 
    }
}

Run your application by typing from the root of the project: mvn quarkus:dev
Access the defined REST endpoint at localhost:8081/myapp and see the output.

Configuration class has its dedicated package and by doing so, you are separating configuration from code. Do you see how easy it is to access your properties!? Basically, you just do it by injecting the configuration class and accessing its fields.

Using the ConfigProperties annotation with interfaces

This option is quite similar to the previous one. The path through which you set up properties is done by java interfaces rather than java classes.
Let's take a look at how this configuration differs from the previous one.

Create UserConfiguration.java file under the src/main/java/config folder, with the following content:

import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.quarkus.arc.config.ConfigProperties;

@ConfigProperties(prefix = "user")
public interface UserConfiguration {
    @ConfigProperty(name = "status")
    String status();

    Post posts();

    interface Post {
        @ConfigProperty(name = "min")
        String min();
        @ConfigProperty(name = "max")
        String max();
    }
}

Your ApplicationResource.java file will be rewritten with:

@Path("/myapp")
public class ApplicationResource {
    @Inject UserConfiguration userConfiguration;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String print() {
        return "Status: " + userConfiguration.status()
                 + " Minimum allowed posts: " + userConfiguration.posts().min()
                 + " Maximum allowed posts: " + userConfiguration.posts().max(); 
    }
}

However, by utilizing interfaces you can create more wide config structure. Configuration interface can extend multiple other interfaces. For example, let's create another interface called IUserBiography.java:

import org.eclipse.microprofile.config.inject.ConfigProperty;

public interface IUserBiography {
    @ConfigProperty(name = "residence")
    String residence();
}

This interface designed to contain required specific information about the user. To simplify things let's say we're interested in the user's residence information. The UserConfiguration.java interface will be slightly changed:

import org.eclipse.microprofile.config.inject.ConfigProperty;
import io.quarkus.arc.config.ConfigProperties;

@ConfigProperties(prefix = "user")
public interface UserConfiguration extends IUserBiography {
    @ConfigProperty(name = "status")
    String status();

    Post posts();

    interface Post {
        @ConfigProperty(name = "min")
        String min();
        @ConfigProperty(name = "max")
        String max();
    }
}

Now UserConfiguration.java interface inherits properties from the IUserBiography.java interface and thus we have a centralized interface to access all user-related properties. The new property is nested under user. prefix i.e. user.residence.

To omit chaining of interface methods to access the particular property, we can present a new java class that will shorten properties pull out. The new class should have Singleton scope and will be used for injection as a configuration class.

Create UserConfigurationFromInterface.java class with the following content:

@Singleton
public class UserConfigurationFromInterface {
    @Inject
    UserConfiguration userConfiguration;

    public String getResidence() {
        return userConfiguration.residence();
    }

    public String getStatus() {
        return userConfiguration.status();
    }

    public String getPostsMin() {
        return userConfiguration.posts().min();
    }

    public String getPostsMax() {
        return userConfiguration.posts().max();
    }
}

Your ApplicationResource.java file will be rewritten with:

@Path("/myapp")
public class ApplicationResource {
    @Inject UserConfigurationFromInterface userConfiguration;

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String print() {
        return "Status: " + userConfiguration.getStatus()
                 + " Minimum allowed posts: " + userConfiguration.getPostsMin()
                 + " Maximum allowed posts: " + userConfiguration.getPostsMax()
                 + " Residence: " + userConfiguration.getResidence(); 
    }
}

Run your application by typing from the root of the project: mvn quarkus:dev
Access the defined REST endpoint at localhost:8081/myapp and see the output. The output will be almost identical to what we saw by using ConfigProperties with classes with residence information added.

You can find the link to the GitHub project at the end of this article.

Summary

Today we have learned how Quarkus and MicroProfile Config makes your application configuration an easy affair. There are three main options for how it can be done, which can be used independently of each other. Beware that Quarkus 2.0 will be based on MicroProfile Config 2.0 which will introduce ConfigProperties annotation. Separating configuration from code is a good practice that can show your competence in the area.

GitHub project: github.com/kshpak/quarkus-and-microprofile-..
12-factor methodology: 12factor.net

Hope you liked this article. See you at the next one. Peace ;D
The Breakfast Bar