Configuring Microservices with MicroProfile Configuration

In the era of Microservices it is essential to be able to externalize and inject both static and dynamic configuration properties for your services. As a matter of fact, microservices are designed to be moved across different environments, therefore it is essential to have a portable externalization of their configuration. In this tutorial we will learn how to use the MicroProfile Configuration API on top of WildFly Application Server.

Since WildFly 14 this can be done with the addition of SmallRye implementation of MicroProfile Configuration.

The MicroProfile Config SmallRye subsystem

The MicroProfile config has been implemented in WildFly with the following subsystem:

<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0"/>

This subsystem in turns requires the following extension:

<extension module="org.wildfly.extension.microprofile.config-smallrye"/>

When this subsystem is activated, the configuration will be injected into the org.eclipse.microprofile.config.Config object. This configuration object contains the information collected from several configuration locations, called ConfigSources and stored in org.eclipse.microprofile.config.spi.ConfigSource. By default there are 3 default ConfigSources:

  1. System.getProperties()
  2. System.getenv()
  3. All META-INF/microprofile-config.properties files on the ClassPath

In addition, all entries in microprofile-config-smallrye subsystem will be also included as Config Source. The following picture depicts the list of ConfigSources which can be captured by WildFly Application Server:

If the same property is defined in multiple ConfigSources, the policy is that the one with highest ordinal number overwrite the lower (So for example if you start the application server with a System Property named “foo”, it will override another property named foo in the microprofile-config.properties.

Now let’s see all supported ConfigSources locations:

ConfigSources in microprofile-config-smallrye subsystem

You can store properties in the ConfigSource by adding them in the microprofile-config-smallrye subsystem. Let’s see an example:

/subsystem=microprofile-config-smallrye/config-source=props:add(properties={"property1" = "value1", "property2" = "value2"})

In terms of XML configuration, this is the outcome:

<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0">
    <config-source name="props">
        <property name="property1" value="value1"/>
        <property name="property2" value="value2"/>
    </config-source>
</subsystem>

You can reference also properties as files from a directory. For example, if the folder /var/config contains the files:

  • text1
  • text2

We can create a config-source using as target the folder /var/config:

/subsystem=microprofile-config-smallrye/config-source=file-props:add(dir={path=/var/config})

Now our ConfigSource will contain two entries: text1 and text2 and the value is the content of those files.

This results in the XML configuration:

<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0">
    <config-source name="file-props">
        <dir path="/var/config"/>
    </config-source>
</subsystem>

With that configuration, any application deployed in WildFly can use the above ConfigSource as property. Here is a Servlet example which reads the property1 and property2:

package com.mastertheboss.mp;

import java.io.IOException;
import javax.inject.Inject;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.microprofile.config.inject.ConfigProperty;

@WebServlet(name = "config", urlPatterns = { "/config" })

public class ConfigServlet extends HttpServlet {

    @Inject
    @ConfigProperty(name = "text1")
    String prop1;

    @Inject
    @ConfigProperty(name = "text2", defaultValue="Hello World")
    String prop2;

    public ConfigServlet() {
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getWriter().append("Got property1 with: ").append(prop1);
        response.getWriter().append("Got property2 with: ").append(prop2);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

Please note from the above code that it is possible to define a default value for a @ConfigProperty, which will be used in case the property has not been defined in any configuration scope:

    @Inject
    @ConfigProperty(name = "text2", defaultValue="Hello World")
    String prop2;

Also, if you don’t want to reference each single ConfigProperty, you can also inject the generic org.eclipse.microprofile.config.Config object:

@Inject Config config;

The Config class can also be retrieved from non CDI Beans, by using the following static method:

Config config = ConfigProvider.getConfig();

Whatever is your strategy to access the org.eclipse.microprofile.config.Config class, you can use it to retrieve each single Property with its getValue method:

String p =  config.getValue("property1", String.class);

You can also use the getPropertyNames() method to iterate over the list of Properties:

Iterable<String> i = config.getPropertyNames();

In order to compile your project, you’d need to include, besides the EE dependencies, start by including the following Bill Of Material in your pom.xml:

<dependency>
    <groupId>org.wildfly.bom</groupId>
    <artifactId>wildfly-microprofile</artifactId>
    <version>${wildfly.version}</version>
    <scope>import</scope>
    <type>pom</type>
</dependency>

Next, include the microprofile-config-api dependency:

<dependency>
	<groupId>org.eclipse.microprofile.config</groupId>
	<artifactId>microprofile-config-api</artifactId>
</dependency>

One advantage of using the MicroProfile strategy over standard configuration files is that property Injection will be checked at deployment time:

Caused by: org.jboss.weld.exceptions.DeploymentException: Error while validating Configuration:
No Config Value exists for prop1

ConfigSource from Class

You can also provide a ConfigSource class which implements org.eclipse.microprofile.config.spi.ConfigSource and acts as source for the ConfigSource. For example, you can provide an implementation of org.eclipse.microprofile.config.spi.ConfigSource that is named com.mastertheboss.MyConfigSource :

package com.mastertheboss.mp;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

public class MyConfigSource implements ConfigSource {

    String fileLocation = System.getProperty("user.dir") + "resources/config.properties";

    @Override
    public int getOrdinal() {
        return 400;
    }

    @Override
    public Set<String> getPropertyNames() {
        return getProperties().keySet();
    }

    @Override
    public String getValue(String key) {
        return getProperties().get(key);
    }

    @Override
    public String getName() {
        return "Custom Config Source: file:" + this.fileLocation;
    }

    @Override
    public Map<String, String> getProperties() {
        Map<String, String> map = new HashMap<String, String>();

        Properties properties = new Properties();
        InputStream in;
        try {
            in = new FileInputStream(this.fileLocation);
            properties.load(in);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        for (final String name : properties.stringPropertyNames())
            map.put(name, properties.getProperty(name));

        return map;

    }

}

The class needs to be registered as module and installed. For example, if the class is packaged in a library and installed as “org.mp” module:

/subsystem=microprofile-config-smallrye/config-source=my-config-source:add(class={name=com.mastertheboss.MyConfigSource, module=org.mp})

This results in the XML configuration:

<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0">
    <config-source name="my-config-source">
        <class name="com.mastertheboss" module="org.mp"/>
    </config-source>
</subsystem>

ConfigSources in microprofile-config.properties

Finally, the application is able to extract the ConfigSource by including them into the META-INF/microprofile-config.properties:

ConfigSources from the property file have priority (in case of clash between properties) over the ConfigSources from the subsystem. However they are overridden by System Properties and Env Properties.

Bulk extraction of properties

Since Microprofile Config 2.0 you can use the @ConfigProperties annotation to allow the bulk extraction of properties into a property class. This can be used to avoid the repeating of @ConfigProperty annotation on a number of related properties read from a configuration source. For example, consider the following configuration:

db.host=localhost
db.port=5432
db.user=admin
db.password=secret  

It would be now possible to extract all the properties into one class:

@ConfigProperties(prefix = "db")
public class DBConfig {
  String host;
  Integer port;
  String user;
  String password;
}

The source code for this example is available at: https://github.com/fmarchioni/mastertheboss/tree/master/micro-services/mp-config-example

WildFly Administration Guide

This article is an excerpt from “WildFly Administration Guide“, the only book which is always updated with the newest features of the application server!