MicroProfile

A previous article described the specifications in the Eclipse MicroProfile 1.2 release and the benefits for Java-based cloud-native applications. This article shows how software developers writing Java-based microservices can leverage those specifications to take advantage of the management capabilities provided by Red Hat OpenShift.

The MicroProfile 1.2 release provides specifications to solve the common challenges that Java developers are facing when building and deploying microservices on container platforms:

  • The Config specification provides a uniform way to configure Java applications regardless of the deployment environment (local JVM, testing environment for continuous integration, container platform for production deployment, etc.).
  • The Health Check specification provides a single HTTP endpoint that can be queried to determine the overall status of the application (that can be composed internally of many different and unrelated Health Checks).

The purpose of this article is to provide an overview of the MicroProfile Config and Health Check specifications.  It will also illustrate how a Java application can use the APIs to integrate with the management capabilities provided by container/cloud platforms, such as Red Hat OpenShift or Kubernetes.

Eclipse MicroProfile

As stated on its website, the mission of the Eclipse MicroProfile is to define:

An open forum to optimize Enterprise Java for a microservices architecture by innovating across multiple implementations and collaborating on common areas of interest with a goal of standardization.

The Eclipse MicroProfile project provides an umbrella of Java specifications to develop and deploy microservice-based Java applications. Some of these specifications are coming from Java EE (like JAX-RS or JSON-P), but Eclipse MicroProfile also specifies new ones that fill gaps not covered by existing Java specifications or APIs.

In this article, we will focus on the two specifications from the MicroProfile 1.2 release that relates to application configuration and healthiness:

  • MicroProfile Config
  • MicroProfile Health Check

MicroProfile Config Specification

The MicroProfile Config specification is the first specification from the Eclipse MicroProfile project. It provides a common way to retrieve configuration coming from a variety of sources (properties file, system properties, environment variables, database, etc.). The API is very simple and consists mainly of 2 objects:

  • a Config interface can be used to retrieve (possibly optional) values identified by a name from many config sources.
  • a @ConfigProperty annotation to directly inject a configuration value using CDI.

A sample code to use for the Config API looks like this:

@Inject
@ConfigProperty(name = "num_size", defaultValue = "3")
private int numSize;

@Inject
@ConfigProperty(name = "num_max", defaultValue = "100")
private int numMax;

 

The @ConfigProperty annotation accepts a defaultValue parameter that will be used to set the field if the property is not present in the configuration.

Alternatively, you can use an injected Config object to get a property value:

@Inject
Config config;
...
Optional<URL> url = config.getOptionalValue("my_url", URL.class);
...

 

The Config object provides two methods to retrieve properties:

  • getValue(String, Class)
  • getOptionalValue(String, Class)

getValue() will throw an exception if the property is not present in the configuration. This method must be used only for mandatory configuration.

getOptionalValue() returns a java.util.Optional object that is empty if the property is not present in the configuration. This method is used for optional configuration.

Both methods will also throw exceptions if the property value (retrieved as a String from the configuration) cannot be converted to the expected Java type passed as the second argument.

ConfigSource

The API also provides a way to add different config sources from where the properties are fetched.

By default, they can come from:

  • JVM System properties (backed by System.getProperties())
  • OS environment (backed by System.getenv())
  • Properties file (stored in the application archive in META-INF/microprofile-config.properties)

Implementations of the Config API can provide additional config sources (for example by reading properties from a relational database or a key-value store).

Applications that use the Config API can also provide custom config source by implementing the org.eclipse.microprofile.config.spi.ConfigSource and registers it in a file /META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource with the fully qualified class name of the custom implementation.

Converters

The MicroProfile Config specification will automatically try to convert the property value (which is retrieved as a String) to the specified type (like URL in the code example above) based on well-known conversion methods (for example, a constructor with a String parameter but also static methods named parse(CharSequence) or valueOf(String)).

If the application uses a Java type that does not conform to these naming conventions, the applications can provide additional Converters to handle any custom-made conversion. The application must provide an implementation of org.eclipse.microprofile.config.spi.Converter and registers it in a file /META-INF/services/org.eclipse.microprofile.config.spi.Converter with the fully qualified class name of the custom implementation.

Most Java types that map to configuration values (String, primitive types such as long or boolean, but also classes like java.time or URL) are supported by the implicit Converters required by the MicroProfile Config specification.

MicroProfile Health Check Specification

The Eclipse MicroProfile Health Check specification defines a single container runtime mechanism for validating the availability and status of a MicroProfile application. This is primarily intended as a mechanism for use in containerized environments such as OpenShift or Kubernetes.

The specification defines a single /health HTTP endpoint that can be queried by the container platform to determine the status of the application running inside the container.

The specification also defines a Java API to create application specific health checks:

@FunctionalInterface
public interface HealthCheck {
  HealthCheckResponse call();
}

When the container queries the /health endpoint, the MicroProfile Health Check implementation calls all registered HealthCheck instances to determine the overall status of the application.

The HealthCheckResponse returned by the HealthCheck instance contains a name, an outcome, and optional data. For example:

public class CheckDiskspace implements HealthCheck {
  @Override
  public HealthCheckResponse call() {
    return HealthCheckResponse.named("diskspace")
             .withData("free", "780mb")
             .up()
             .buid();
  }
}

When the application is started, the MicroProfile HealthCheck implementations load the HealthCheck (that is annotated with the @Health annotation) using CDI.

The container can then call the /health endpoint to determine the overall status of the application.

$ curl -v http://localhost:8080/health
...
< HTTP/1.1 200 OK
...
{"checks": [
{"name":"diskspace","state":"UP", "data":{"free": "780mb"}}],
"outcome": "UP"
}

 

If the overall outcome of the Health Check is UP, the HTTP returns a 200 OK response.

If one of more of the HealthChecks would return DOWN, the overall outcome of the Health Check would be DOWN and the HTTP request would return a 503 Service Unavailable response instead.

Build and deploy a MicroProfile Application on OpenShift

To illustrate the use of MicroProfile API in a Java application, we will use a very simple Java application running with WildFly Swarm. We will describe it and test it locally first. Then we will deploy it on OpenShift and illustrates how the MicroProfile Config and HealthCheck can leverage the OpenShift container platform.

Description of the Application

This application is a simple Web Service with a unique HTTP endpoint that returns a list of random integers.

The source code for the application is hosted at https://github.com/jmesnil/microprofile-openshift-example/.

When a user sends a request to http://localhost:8080/, the application responds with a list of random integers in a JSON array:

$ curl http://localhost:8080/
[630030638,1689535734,1995796600]

Application Configuration

The application uses MicroProfile Config to configure its behavior. It defines two configuration properties:

  • num_size - the number of generated random integers (3 by default)
  • num_max - the maximum value of integer (100 by default)

These two properties are configured using the Eclipse MicroProfile Config API in the NumbersGenerator class:

@Inject
@ConfigProperty(name = "num_size", defaultValue = "3")
private int numSize;

@Inject
@ConfigProperty(name = "num_max", defaultValue = "100")
private int numMax;

 

In the absence of a more specific configuration, the application will return a list of 3 integers between 0 and 100.

Application Health check

Our demo application is quite simple and does not really require you to define Health Checks. However we will add one just to illustrate how they can fit in more realistic applications.

We will add a single Health Check to determine the healthiness of the NumbersGenerator. The Health Check will check the configuration of the NumbersGenerator and will return DOWN if the configuration is not correct (in a real example it would be better to prevent the application to start if it is misconfigured).

@ApplicationScoped
@Health
public static class ConfigHealthCheck implements HealthCheck {

  private static final String PROBE_NAME = "numbers.config";

  @Inject
  NumbersGenerator generator;

  @Override
  public HealthCheckResponse call() {
    if (generator.numSize < 0 ||
        generator.numMax <= MIN_VALUE) {
      return HealthCheckResponse.named(PROBE_NAME)
               .withData("num_size", generator.numSize)
               .withData("num_max", generator.numMax)
               .down()
               .build();
    } else {
      return HealthCheckResponse.named(PROBE_NAME)
              .up()
              .build();
    }
  }
}

 

If the NumbersGenerator is correctly configured, the Health Checks will return UP. If it is misconfigured, the Health Check will return DOWN as well as the configuration values that are causing the issue.

Build the application and test it locally

The application is built using Maven:

$ mvn clean package

The application is packaged as a Java UberJar that can be run locally:

$ java -jar target/numbers-swarm.jar
...
2018-01-15 11:58:50,658 INFO [org.jboss.as.server] (main) WFLYSRV0010: Deployed "numbers.war" (runtime-name : "numbers.war")
2018-01-15 11:58:50,677 INFO [org.wildfly.swarm] (main) WFSWARM99999: WildFly Swarm is Ready

 

We can go repeatedly to http://localhost:8080/ to generate lists of random integers:

$ curl http://localhost:8080/
[99,38,43]
[75,43,75]
[9,55,69]

We can now change the configuration of the application by using environment variables to specify different values for the num_size and num_max properties.

$ num_size=5 num_max=10 java -jar target/numbers-swarm.jar

The application will now return a list of 5 random integers between 0 and 10:

[2,2,5,9,7]
[6,4,0,0,2]
[1,9,4,0,3]
[1,9,5,3,6]

We could provide the same configuration using System properties (using -D on the command line):

$ java -jar target/numbers-swarm.jar -Dnum_size=5 -Dnum_max=10

 

The MicroProfile Config specifies that each config source has an ordinal value that determines which config source will provide the actual property value to the application. If a property is available in multiple config sources, the one with the highest ordinal wins.

The MicroProfile Config specifies that properties coming from the environment variables have an ordinal of 300 while properties coming from the JVM System properties have an ordinal of 400.

That means that if the num_size property is defined both in the environment and the JVM system properties, the value used by the application comes from the JVM System properties:

$ num_size=5 java -jar target/numbers-swarm.jar -Dnum_size=1

In that example above, the application will use 1 for the value of its num_size configuration property:

$ curl http://localhost:8080/
[96]

We can also test the Health Check locally:

$ curl -v http://localhost:8080/
< HTTP/1.1 200 OK
...
{"checks": [
{"name":"numbers.config","state":"UP"}],
"outcome": "UP"
}

If we are starting the application with an incorrect configuration (e.g. using a num_max of -1), the Health Check will return DOWN:

$ java -jar target/numbers-swarm.jar -Dnum_max=-1
...

$ curl -v http://locahost:8080/health
< HTTP/1.1 503 Service Unavailable
...
{"checks": [
{"name":"numbers.config","state":"DOWN","data": {"num_size":3,"num_max":-1}}],
"outcome": "DOWN"
}

 

 

Deploy on OpenShift

We have tested the application locally. Now let's deploy it on OpenShift.

The application uses the fabric8 Maven plugin to deploy on OpenShift. It has a few prerequisites:

  • Sign up to https://manage.openshift.com
  • Ensure that the oc command line is configured to point to our OpenShift account (once you are logged in OpenShift Web Console, you can use the "Copy Login Command" and paste it into your terminal)
$ oc login https:/xxxxx.openshift.com --token=xxxxx
$ oc status

Once you are logged in from the command line, you can deploy the application by running the Maven command:

$ mvn -Popenshift clean fabric8:deploy

Maven uses the openshift profile to deploy the application on OpenShift under the name microprofile-openshift-example.

The Java application is configured to automatically expose a route to its 8080 port. To find the name of the exposed route to interact with the application, we can look at the application configuration with the oc command:

$ oc get routes/microprofile-openshift-example
NAME                             HOST/PORT                                                                        PATH      SERVICES                         PORT      TERMINATION   WILDCARD
microprofile-openshift-example   microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com              microprofile-openshift-example   8080

 

If you deploy the application with your own OpenShift account, the URL will be different from mine, which is: http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com.

At this point, the application is accessible and returns a list of random integers:

$ curl http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com
[94,48,3]

 

 

OpenShift services for MicroProfile application

OpenShift Environment Configuration

We will now modify the OpenShift environment so that it provides the configuration value for the application.

Using the oc tool, we can list the environment variables for our microprofile-openshift-example application:

$ oc env dc/microprofile-openshift-example --list
# deploymentconfigs microprofile-openshift-example, container wildfly-swarm
# KUBERNETES_NAMESPACE from field path metadata.namespace

 

We can now specify that we want to return a list of 5 random integers between 0 and 10:

$ oc env dc/microprofile-openshift-example num_size=5 num_max=10

 

OpenShift detects that the environment has changed and will roll out the application to deploy a version with the updated environments.

Once the deployment has been rolled out, we can check that the list of random integers returned by our application now contains 5 random integers between 0 and 10.

Check here: http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com

$ curl http://microprofile-openshift-example-microprofile-demo.1d35.starter-us-east-1.openshiftapps.com
[9,2,9,5,1]

 

OpenShift Liveness and Readiness Probes

The readiness probe is automatically configured in OpenShift to use the /health endpoint of our application. This configuration is extracted from the configuration of the fabric8 Maven plugin is configured in the application’s pom.xml:

<plugin>
 <groupId>io.fabric8</groupId>
 <artifactId>fabric8-maven-plugin</artifactId>
 ...
 <configuration>
   ...
   <enricher>
     <config>
       <wildfly-swarm-health-check>
         <path>/health</path>
       </wildfly-swarm-health-check>
     </config>
   </enricher>
 </configuration>
</plugin>

In the OpenShift Web Console, the deployment page for the application lists the probes:

  • Readiness Probe: GET /health on port 8080 (HTTP) 10s delay, 1s timeout
  • Liveness Probe: GET /health on port 8080 (HTTP) 180s delay, 1s timeout

The oc tool can be used to check that they are properly configured too:

$ oc describe dc/microprofile-openshift-example
Name:           microprofile-openshift-example
...
  Containers:
   wildfly-swarm:
    Liveness:   http-get http://:8080/health delay=180s timeout=1s period=10s #success=1 #failure=3
    Readiness:  http-get http://:8080/health delay=10s timeout=1s period=10s #success=1 #failure=3

 

Summary

The MicroProfile 1.2 release and its specifications help developing and deploying MicroServices-based Java applications.

In this article, we have used the MicroProfile Config specification to provide a uniform configuration to the Java application. We have then shown how to provide different values to the configuration using System properties or environment variables.

We have also used the MicroProfile Health Check specification to define health probes and provide a HTTP entry point that container platforms such as OpenShift can use to query the healthiness of the Java application.

Finally, we have deployed the Java application on OpenShift and demonstrated how to set environment variables to configure the application. We have also configured OpenShift liveness and readiness probes to use the MicroProfile Health Check HTTP entry point to check the application healthiness.

Contribute and Collaborate

The Microprofile is an open and vibrant community hosted under the Eclipse Foundation. This, together with the decision to move future Java EE specification work under the Eclipse foundation, provides an excellent opportunity to provide feedback, contribute, and collaborate.

Eclipse Microprofile projects

Microprofile 1.2 Specifications

Discussion Groups and Mailing lists

Last updated: January 12, 2024