tech-trends

First Step - JEE Microservices with MicroProfile-Config

// 10-04-2020

The benefits of using Microprofile-Config

MicroProfile-Config allows us to provide configurations to our applications without referencing the configuration source from within the application and provides the possibility to inject configurations into CDI beans. MicroProfile-Config abstracts the application code from the underlying configuration source and therefore configurations can be provided to the application by different configurations sources.  As with all MicroProfiles, MicroProfile-Opentracing is also not restricted to be used only in microservices but can also be useful in ordinary WAR/EAR deployments which run on an application server.

How to integrate MicroProfile-Config into an application

The MicroProfile-Config API and it’s implementations are supplied via Maven Dependencies, although these dependencies are provided in different ways, depending on the used application runtime. The following sections explain how MicroProfile-Opentracing is supplied by different application runtimes.

Integration in Wildfly/EAP

The cited dependency has to be added in provided scope. No runtime dependency is needed, because Wildfly/EAP has MicroProfile capabilities already installed as JBoss modules.

<dependency>
     <groupId>org.eclipse.microprofile.config</groupId>
     <artifactId>microprofile-config-api</artifactId>
     <version>${version.microprofile.config}</version>
     <scope>provided</scope>
</dependency>

Integration with Thorntail

Thorntail provides a Maven Bill-of-Materials (bom) which includes the Thorntail fraction for MicroProfile-Config.

<dependencyManagement>
      <dependencies>
          <dependency>
              <groupId>io.thorntail</groupId>
              <artifactId>bom</artifactId>
              <version>${version.thorntail}</version>
              <scope>import</scope>
              <type>pom</type>
          </dependency>
      </dependencies>
  </dependencyManagement>

  <dependencies>
      <dependency>
          <groupId>io.thorntail</groupId>
          <artifactId>microprofile-config</artifactId>
      </dependency>
  </dependencies>

Integration with Quarkus

Quarkus, like Thorntail, provides a Maven-BOM which provides all Extensions supported by the specific Quarkus version.

<dependencyManagement>
  <dependencies>
       <dependency>
           <groupId>io.quarkus</groupId>
           <artifactId>quarkus-bom</artifactId>
           <version>${quarkus.version}</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>
   </dependencies>
  </dependencyManagement>

  <dependencies>
        <!-- Does not need a dependency defined. 
             It uses Smallrye implementation of 
             the MicroProfile-Config as default 
             for configurations -->
  </dependencies>

How to provide configurations to an application

This section shows how to provide configurations to an application in different environments via different configuration sources.

Supported Configuration Sources

The specification demands the following configuration sources to be supported by any implementation and processed in the given order:

  • System Properties
  • Environment variables
  • META-INF/microprofile-config.properties

Configurations provided by a configuration source of a higher ordinal will overlay configurations provided by a configuration source of a lower ordinal.

Custom configuration sources can be implemented as well and will be discovered via SPI. For instance, the Smallrye implementation provides a DirectoryConfigSource which processes configurations from files located in a configured directory (nested directories are not supported), in which the filename represents the configuration name and the file content the configuration value. This is very useful in cases where the application runs in a Linux Container in the cloud.

How to provide configuration for Wildfly/EAP

Configurations for an application running on a Wildfly/EAP can be provided as  system properties defined in the standalone.xml, environment variables defined in the host system or configurations provided by the Smallrye subsystem.

System properties in standalone.xml:
 <system-properties>
    <property name="conax.request.dir" value="/tmp/cmnt/req"/>
 </system-properties>


Properties in the subsystem in standalone.xml:
 <subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0">
    <config-source name="props">
        <property name="prop1" value="foo"/>
        <property name="prop2" value="bar"/>
    </config-source>
</subsystem>


Directory configuration in the subsystem in standalone.xml:
 <subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0">
  <config-source name="file-props">
      <dir path="/etc/config/numbers-app"/>
  </config-source>
 </subsystem>


 Configuring directory source via the JBoss-CLI:          
 /subsystem=microprofile-config-smallrye/config-source=file-props \   
           :add(dir={path=/etc/config/numbers-app})


Configuring properties via the JBoss-CLI:   
 /subsystem=microprofile-config-smallrye/config-source=props \
           :add(properties={"prop1" = "foo", "prop2" = "bar"})

How to provide configurations for Thorntail

Configurations for an application running on Thorntail might be provided via system properties defined during startup, via environment variables defined by the host Linux Container or via configurations defined in YAML files within the application (supports stages). The YAML files might also be put outside the application and provided via startup arguments.

There are multiple ways to configure Thorntail via YAML files which are explained in the following list.

  • src/main/resources/microprofile-config.properties
    The configuration file which is enforced by the specification to be supported by any implementation
  • src/main/resources/project-defaults.yaml
    This file should contain the default configurations applicable for all stages
  • src/main/resources/project-stages.yaml
    This file should contain configurations for multiple stages separated via ‘– – -’, which indicates that this YAML file contains actually multiple YAML files. The stages can be selected via startup arguments or a system property like this:
    // Activates all stages contained in project-stages.yaml
    java -jar app-thorntail.jar -Slocal,test


    // Activates one single stage in project-stages.yaml
    java -Dthorntail.project.stage=test -jar app-thorntail.jar
  • src/main/resources/project-test.yaml
    Configuration file for the test stage. The configuration file can be activated via a startup argument like, for example:
    // Activates all stages contained in project-stages.yaml
    java -jar app-thorntail.jar -Stest
  • /some_dir/external-config.yaml
    Contains the configuration outside the application which can be provided via startup arguments:
    java -jar app-thorntail.jar -s /some_dir/external-config.yaml

The cited example shows a YAML file  project-stages.yaml which contains configurations for the stages ‘local’, ‘test’ and ‘cloud’.

Staged MicroProfile-Config configuration: 
  # Configurations for all stages
  thorntail:
    microprofile:
      config:
        config-sources:
          STATIC_CONFIG:
            properties:
              info: "The application info..."
  — — -
  project:
    stage: local 
  thorntail:
    microprofile:
      config:
        config-sources:
          LOCAL_CONFIG:
            properties:
              prop: "local-prop"
  — — -
  project:
    stage: test
  thorntail:
    microprofile:
      config:
        config-sources:
          TEST_CONFIG:
            properties:
              prop: "test-prop" 
  — — -
  project:
    stage: cloud
  thorntail:
    microprofile:
      config:
        config-sources:
          CLOUD_CONFIG:
            Dir: “/config/app”

How to provide configurations for Quarkus

Configurations for an application running with Quarkus can be provided via system properties defined during startup, environment variables defined by the hosting Linux Container or via configurations defined in a properties file within the application.

There are multiple ways to configure Quarkus which are explained in the following list.

  • src/main/resources/microprofile-config.properties
    The configuration file which is enforced by the specification to be supported by any implementation
  • src/main/resources/application.properties
    The Quarkus specific configuration file
  • Via system properties during startup
    java -Dproperty=value -jar app-quarkus.jar

I am not familiar with Quarkus yet, and just used it to illustrate that a JEE application can run oin multiple application runtimes.

How to implement advanced behaviours for configuration

Configurations provided for an application via MicroProfile-Config are simple String variables, but the demand for other variable types such as Integer, Double or Boolean in configurations might arise. The need for support of the standard Java Types definitely exists.

Configuration Converters

If you have configuration properties of a specific format then you can convert them to any Java Type you wish via  custom converters. Custom converters are resolved by MicroProfile-Config via SPI.

The following code snippet illustrates how to map a configuration property of the form
<PROPERTY_NAME>=<USERNAME>;<FIRST_NAME> to a custom Java Model.
// The user model
public class User {
  public final String username;
  public final String firstname;

  public User(String username, String firstname) {
    this.username = username;
    this.firstname = firstname;
  }
}

// Converts a property to a User model.
// E.g user.extern=het;thomas
public class UserConverter implements Converter<User> {
  @Override
  public User convert(String value) {
    final String[] properties = value.split(";");
    final String username  = properties[0];
    final String firstname  = properties[1];
    return new User(username, firstname);
  }
}

How to provide a configuration within an application

MicroProfile-Config integrates into CDI and makes the configurations injectable via the CDI container. Some applications even spread the configurations through the whole application where it becomes hard over time to find usages of configurations in the application code. System.getProperty(“prop”) is mostly used to retrieve configurations within applications whereby the configuration source is limited to system properties and always the current configuration value is retrieved. For instance, if we have a request and we would replace a configuration property, an inconsistency during the request could happen because some parts of the application use the old configuration value and others the new configuration value.

With MicroProfile-Config and CDI it is now possible to implement a scoped configuration class which bundles all configurations, has no reference to the underlying configuration source and reloads the configurations whenever the bean scope is created.

Configuration classes

The following code snippet illustrates a configuration class which exists in the RequestScope (@RequestScoped) and bundles all configurations for the application. Each request has its own bean and keeps the state consistent over the request lifecycle. The injection of the configurations is performed via a CDI Producer Method, provided by the MicroProfile-Config implementation, because @ConfigProperty is a CDI qualifier annotation.

@RequestScoped
public class Configuration {

  @Inject
  @ConfigProperty(name = "info.text", defaultValue=“...”)
  private String infoText;

  @Inject
  @ConfigProperty(name = "google.apiKey")
  private String apiKey;

  // Getters for accessing the configuration properties
}

Be aware that the reload of configuration properties depends on the used configuration source and implementation vendor because the specifications don’t specify reload behaviours for configuration sources.

Sample Application

I have implemented a simple sample application which demonstrates the usage of MicroProfile-Config in a JEE application which is deployable as a microservice for Thorntail and Quarkus or as a WAR deployment in Wildfly.

Sample

Useful Links

// Autor

Thomas