Configuring microservices

duration 20 minutes

Prerequisites:

Learn how to provide external configuration to microservices using MicroProfile Config.

What you’ll learn

You will learn how to externalize and inject both static and dynamic configuration properties for microservices using MicroProfile Config.

You will learn to aggregate multiple configuration sources, assign prioritization values to these sources, merge configuration values, and create custom configuration sources.

The application that you will be working with is an inventory service which stores the information about various JVMs running on different hosts. Whenever a request is made to the inventory service to retrieve the JVM system properties of a particular host, the inventory service will communicate with the system service on that host to get these system properties. You will add configuration properties to simulate if a service is down for maintenance.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-microprofile-config.git
cd guide-microprofile-config

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Before you begin, make sure you have all the necessary prerequisites.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first go to the finish directory and run the following Maven goal to build the application and deploy it to Open Liberty:

cd finish
mvn liberty:run

After you see the following message, your application server is ready:

The defaultServer server is ready to run a smarter planet.

You can access the following two microservices to test their availability:

In addition, you can access a third microservice, which retrieves and aggregates all of the configuration properties and sources that are added throughout this guide. This is available at:

After you are finished checking out the application, stop the Open Liberty server by pressing CTRL+C in the command-line session where you ran the server. Alternatively, you can run the liberty:stop goal from the finish directory in another shell session:

mvn liberty:stop

Ordering multiple configuration sources

Now, navigate to the start directory to begin.

When you run Open Liberty in development mode, known as dev mode, the server listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following goal to start Open Liberty in dev mode:

mvn liberty:dev

After you see the following message, your application server in dev mode is ready:

**************************************************************
*    Liberty is running in dev mode.

Dev mode holds your command-line session to listen for file changes. Open another command-line session to continue, or open the project in your editor.

MicroProfile Config combines configuration properties from multiple sources, each known as a ConfigSource. Each ConfigSource has a specified priority, defined by its config_ordinal value.

A higher ordinal value means that the values taken from this ConfigSource will override values from ConfigSources with a lower ordinal value.

The following four sources are the default configuration sources:

  • A <variable name="…​" value="…​"/> element in the server.xml file has a default ordinal of 500.

  • System properties has a default ordinal of 400. (e.g. bootstrap.properties file)

  • Environment variables have a default ordinal of 300. (e.g. server.env file)

  • The META-INF/microprofile-config.properties configuration property file on the classpath has a default ordinal of 100.

Access the src/main/resources/META-INF/microprofile-config.properties local configuration file. This configuration file is the default configuration source for an application that uses MicroProfile Config.

microprofile-config.properties

 1# tag::all-props[]
 2# tag::ordinal[]
 3config_ordinal=100
 4# end::ordinal[]
 5# tag::inventory-port-number[]
 6io_openliberty_guides_port_number=9080
 7# end::inventory-port-number[]
 8# tag::inventory-in-maintenance[]
 9io_openliberty_guides_inventory_inMaintenance=false
10# end::inventory-in-maintenance[]
11# tag::system-in-maintenance[]
12io_openliberty_guides_system_inMaintenance=false
13# tag::system-in-maintenance[]
14# tag::test-config-overwrite[]
15io_openliberty_guides_testConfigOverwrite=DefaultSource
16# tag::test-config-overwrite[]
17# end::all-props[]

Injecting static configuration

The MicroProfile Config API is included in the MicroProfile dependency that is specified in your pom.xml file. Look for the dependency with the microprofile artifact ID. This dependency provides a library that allows you to use the MicroProfile Config API to externalize configurations for your microservices. The mpConfig feature is also enabled in the src/main/liberty/config/server.xml file.

pom.xml

  1<?xml version='1.0' encoding='utf-8'?>
  2<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3
  4    <modelVersion>4.0.0</modelVersion>
  5
  6    <groupId>io.openliberty.guides</groupId>
  7    <artifactId>guide-microprofile-config</artifactId>
  8    <version>1.0-SNAPSHOT</version>
  9    <packaging>war</packaging>
 10
 11    <properties>
 12        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 13        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 14        <maven.compiler.source>1.8</maven.compiler.source>
 15        <maven.compiler.target>1.8</maven.compiler.target>
 16        <!-- Liberty configuration -->
 17        <liberty.var.default.http.port>9080</liberty.var.default.http.port>
 18        <liberty.var.default.https.port>9443</liberty.var.default.https.port>
 19    </properties>
 20
 21    <dependencies>
 22        <!-- Provided dependencies -->
 23        <dependency>
 24            <groupId>jakarta.platform</groupId>
 25            <artifactId>jakarta.jakartaee-api</artifactId>
 26            <version>9.1.0</version>
 27            <scope>provided</scope>
 28        </dependency>
 29        <!-- tag::microprofile[] -->
 30        <dependency>
 31            <groupId>org.eclipse.microprofile</groupId>
 32            <artifactId>microprofile</artifactId>
 33            <version>5.0</version>
 34            <type>pom</type>
 35            <scope>provided</scope>
 36        </dependency>
 37        <!-- end::microprofile[] -->
 38        <!-- For tests -->
 39        <dependency>
 40            <groupId>org.junit.jupiter</groupId>
 41            <artifactId>junit-jupiter</artifactId>
 42            <version>5.8.2</version>
 43            <scope>test</scope>
 44        </dependency>
 45        <dependency>
 46            <groupId>org.jboss.resteasy</groupId>
 47             <artifactId>resteasy-client</artifactId>
 48             <version>6.0.0.Final</version>
 49            <scope>test</scope>
 50        </dependency>
 51        <dependency>
 52            <groupId>org.jboss.resteasy</groupId>
 53             <artifactId>resteasy-json-binding-provider</artifactId>
 54             <version>6.0.0.Final</version>
 55            <scope>test</scope>
 56        </dependency>
 57        <dependency>
 58            <groupId>org.glassfish</groupId>
 59            <artifactId>jakarta.json</artifactId>
 60             <version>2.0.1</version>
 61            <scope>test</scope>
 62        </dependency>
 63    </dependencies>
 64
 65    <build>
 66        <finalName>${project.artifactId}</finalName>
 67        <plugins>
 68            <plugin>
 69                <groupId>org.apache.maven.plugins</groupId>
 70                <artifactId>maven-war-plugin</artifactId>
 71                <version>3.3.2</version>
 72            </plugin>
 73            <!-- Plugin to run unit tests -->
 74            <plugin>
 75                <groupId>org.apache.maven.plugins</groupId>
 76                <artifactId>maven-surefire-plugin</artifactId>
 77                <version>2.22.2</version>
 78            </plugin>
 79            <!-- Enable liberty-maven plugin -->
 80            <plugin>
 81                <groupId>io.openliberty.tools</groupId>
 82                <artifactId>liberty-maven-plugin</artifactId>
 83                <version>3.7.1</version>
 84            </plugin>
 85            <!-- Plugin to run functional tests -->
 86            <plugin>
 87                <groupId>org.apache.maven.plugins</groupId>
 88                <artifactId>maven-failsafe-plugin</artifactId>
 89                <version>2.22.2</version>
 90                <configuration>
 91                    <systemPropertyVariables>
 92                      <default.http.port>${liberty.var.default.http.port}</default.http.port>
 93                      <default.https.port>${liberty.var.default.https.port}</default.https.port>
 94                    </systemPropertyVariables>
 95                </configuration>
 96            </plugin>
 97        </plugins>
 98    </build>
 99</project>

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>restfulWS-3.0</feature>
 5    <feature>jsonb-2.0</feature>
 6    <feature>jsonp-2.0</feature>
 7    <feature>cdi-3.0</feature>
 8    <feature>mpRestClient-3.0</feature>
 9    <!-- tag::config[] -->
10    <feature>mpConfig-3.0</feature>
11    <!-- end::config[] -->
12  </featureManager>
13
14  <variable name="default.http.port" defaultValue="9080" />
15  <variable name="default.https.port" defaultValue="9443" />
16
17  <httpEndpoint host="*" httpPort="${default.http.port}"
18    httpsPort="${default.https.port}" id="defaultHttpEndpoint" />
19
20  <webApplication location="guide-microprofile-config.war"
21    contextRoot="/" />
22</server>

Now navigate to the src/main/resources/META-INF/microprofile-config.properties local configuration file to check some static configuration. This configuration file is the default configuration source for an application that uses MicroProfile Config.

The io_openliberty_guides_port_number property that has already been defined in this file, determines the port number of the REST service.

microprofile-config.properties

 1# tag::all-props[]
 2# tag::ordinal[]
 3config_ordinal=100
 4# end::ordinal[]
 5# tag::inventory-port-number[]
 6io_openliberty_guides_port_number=9080
 7# end::inventory-port-number[]
 8# tag::inventory-in-maintenance[]
 9io_openliberty_guides_inventory_inMaintenance=false
10# end::inventory-in-maintenance[]
11# tag::system-in-maintenance[]
12io_openliberty_guides_system_inMaintenance=false
13# tag::system-in-maintenance[]
14# tag::test-config-overwrite[]
15io_openliberty_guides_testConfigOverwrite=DefaultSource
16# tag::test-config-overwrite[]
17# end::all-props[]

To use this configuration property,

Create the InventoryConfig.java class.
src/main/java/io/openliberty/guides/inventory/InventoryConfig.java

InventoryConfig.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::config-class[]
14package io.openliberty.guides.inventory;
15
16import jakarta.enterprise.context.RequestScoped;
17import jakarta.inject.Inject;
18import jakarta.inject.Provider;
19import org.eclipse.microprofile.config.inject.ConfigProperty;
20import io.openliberty.guides.config.Email;
21
22@RequestScoped
23public class InventoryConfig {
24
25  // tag::port-number[]
26  // tag::inject-port-number[]
27  @Inject
28  // end::inject-port-number[]
29  // tag::guides-port-number[]
30  @ConfigProperty(name = "io_openliberty_guides_port_number")
31  // end::guides-port-number[]
32  private int portNumber;
33
34  // end::port-number[]
35  // tag::build-in-converter[]
36  // tag::inject-inMaintenance[]
37  // tag::inject[]
38  @Inject
39  // end::inject[]
40  // tag::configPropety[]
41  @ConfigProperty(name = "io_openliberty_guides_inventory_inMaintenance")
42  // end::configPropety[]
43  // end::inject-inMaintenance[]
44  private Provider<Boolean> inMaintenance;
45
46  // end::build-in-converter[]
47  // tag::custom-converter[]
48  @Inject
49  @ConfigProperty(name = "io_openliberty_guides_email")
50  private Provider<Email> email;
51  // end::custom-converter[]
52
53  // tag::getPortNumber[]
54  public int getPortNumber() {
55    return portNumber;
56  }
57  // end::getPortNumber[]
58
59  // tag::isInMaintenance[]
60  public boolean isInMaintenance() {
61    // tag::inMaintenanceGet[]
62    return inMaintenance.get();
63    // end::inMaintenanceGet[]
64  }
65  // end::isInMaintenance[]
66
67  // tag::getEmail[]
68  public Email getEmail() {
69    return email.get();
70  }
71  // end::getEmail[]
72}
73// end::config-class[]

Inject the io_openliberty_guides_port_number property, and add the getPortNumber() class method to the InventoryConfig.java file.

The @Inject annotation injects the port number directly, the injection value is static and fixed on application starting.

The getPortNumber() method directly returns the value of portNumber because it has been injected.

Injecting dynamic configuration

Note that three default config sources mentioned above are static and fixed on application starting, so the properties within them cannot be modified while the server is running. However, you can externalize configuration data out of the application package, through the creation of custom configuration sources, so that the service updates configuration changes dynamically.

Creating custom configuration sources

CustomConfigSource.json

1{
2  "config_ordinal": 150,
3  "io_openliberty_guides_email": "[email protected]",
4  "io_openliberty_guides_inventory_inMaintenance": false,
5  "io_openliberty_guides_system_inMaintenance": false,
6  "io_openliberty_guides_testConfigOverwrite": "CustomSource"
7}

Custom configuration sources can be created by implementing the org.eclipse.microprofile.config.spi.ConfigSource interface and using the java.util.ServiceLoader mechanism.

A CustomConfigSource.json JSON file has already been created in the resources directory. This JSON file simulates a remote configuration resource in real life. This file contains 4 custom config properties and has an ordinal of 150. To use these properties in the application, the data object needs to be transformed from this JSON file to the configuration for your application.

To link this JSON file to your application and to implement the ConfigSource interface,

Create the CustomConfigSource class.
src/main/java/io/openliberty/guides/config/CustomConfigSource.java

CustomConfigSource.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13// tag::customConfig[]
 14package io.openliberty.guides.config;
 15
 16import jakarta.json.stream.JsonParser;
 17import jakarta.json.stream.JsonParser.Event;
 18import jakarta.json.Json;
 19import java.math.BigDecimal;
 20import java.io.StringReader;
 21import java.util.HashMap;
 22import java.util.Map;
 23import java.util.Set;
 24
 25import java.io.BufferedReader;
 26import java.io.FileReader;
 27import org.eclipse.microprofile.config.spi.ConfigSource;
 28
 29/**
 30 * User-provided ConfigSources are dynamic.
 31 * The getProperties() method will be periodically invoked by the runtime
 32 * to retrieve up-to-date values. The frequency is controlled by
 33 * the microprofile.config.refresh.rate Java system property,
 34 * which is in milliseconds and can be customized.
 35 */
 36public class CustomConfigSource implements ConfigSource {
 37
 38  String fileLocation = System.getProperty("user.dir").split("target")[0]
 39      + "resources/CustomConfigSource.json";
 40
 41  @Override
 42  public int getOrdinal() {
 43    return Integer.parseInt(getProperties().get("config_ordinal"));
 44  }
 45
 46  @Override
 47  public Set<String> getPropertyNames() {
 48    return getProperties().keySet();
 49  }
 50
 51  @Override
 52  public String getValue(String key) {
 53    return getProperties().get(key);
 54  }
 55
 56  @Override
 57  public String getName() {
 58    return "Custom Config Source: file:" + this.fileLocation;
 59  }
 60
 61  // tag::getProperties[]
 62  public Map<String, String> getProperties() {
 63    Map<String, String> m = new HashMap<String, String>();
 64    String jsonData = this.readFile(this.fileLocation);
 65    JsonParser parser = Json.createParser(new StringReader(jsonData));
 66    String key = null;
 67    while (parser.hasNext()) {
 68      final Event event = parser.next();
 69      switch (event) {
 70      case KEY_NAME:
 71        key = parser.getString();
 72        break;
 73      case VALUE_STRING:
 74        String string = parser.getString();
 75        m.put(key, string);
 76        break;
 77      case VALUE_NUMBER:
 78        BigDecimal number = parser.getBigDecimal();
 79        m.put(key, number.toString());
 80        break;
 81      case VALUE_TRUE:
 82        m.put(key, "true");
 83        break;
 84      case VALUE_FALSE:
 85        m.put(key, "false");
 86        break;
 87      default:
 88        break;
 89      }
 90    }
 91    parser.close();
 92    return m;
 93  }
 94  // end::getProperties[]
 95
 96  public String readFile(String fileName) {
 97    String result = "";
 98    try {
 99      BufferedReader br = new BufferedReader(new FileReader(fileName));
100      StringBuilder sb = new StringBuilder();
101      String line = br.readLine();
102      while (line != null) {
103        sb.append(line);
104        line = br.readLine();
105      }
106      result = sb.toString();
107      br.close();
108    } catch (Exception e) {
109      e.printStackTrace();
110    }
111    return result;
112  }
113}
114// end::customConfig[]

The getProperties() method reads the key value pairs from the resources/CustomConfigSource.json JSON file and writes the information into a map.

Finally, register the custom configuration source.

Create the configuration file.
src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource

org.eclipse.microprofile.config.spi.ConfigSource

1io.openliberty.guides.config.CustomConfigSource

Add the fully qualified class name of the configuration source into it.

Enabling dynamic configuration injection

Now that the custom configuration source has successfully been set up, you can enable dynamic configuration injection of the properties being set in this ConfigSource. To enable this dynamic injection,

Replace the InventoryConfig.java class.
src/main/java/io/openliberty/guides/inventory/InventoryConfig.java

InventoryConfig.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::config-class[]
14package io.openliberty.guides.inventory;
15
16import jakarta.enterprise.context.RequestScoped;
17import jakarta.inject.Inject;
18import jakarta.inject.Provider;
19import org.eclipse.microprofile.config.inject.ConfigProperty;
20import io.openliberty.guides.config.Email;
21
22@RequestScoped
23public class InventoryConfig {
24
25  // tag::port-number[]
26  // tag::inject-port-number[]
27  @Inject
28  // end::inject-port-number[]
29  // tag::guides-port-number[]
30  @ConfigProperty(name = "io_openliberty_guides_port_number")
31  // end::guides-port-number[]
32  private int portNumber;
33
34  // end::port-number[]
35  // tag::build-in-converter[]
36  // tag::inject-inMaintenance[]
37  // tag::inject[]
38  @Inject
39  // end::inject[]
40  // tag::configPropety[]
41  @ConfigProperty(name = "io_openliberty_guides_inventory_inMaintenance")
42  // end::configPropety[]
43  // end::inject-inMaintenance[]
44  private Provider<Boolean> inMaintenance;
45
46  // end::build-in-converter[]
47  // tag::custom-converter[]
48  @Inject
49  @ConfigProperty(name = "io_openliberty_guides_email")
50  private Provider<Email> email;
51  // end::custom-converter[]
52
53  // tag::getPortNumber[]
54  public int getPortNumber() {
55    return portNumber;
56  }
57  // end::getPortNumber[]
58
59  // tag::isInMaintenance[]
60  public boolean isInMaintenance() {
61    // tag::inMaintenanceGet[]
62    return inMaintenance.get();
63    // end::inMaintenanceGet[]
64  }
65  // end::isInMaintenance[]
66
67  // tag::getEmail[]
68  public Email getEmail() {
69    return email.get();
70  }
71  // end::getEmail[]
72}
73// end::config-class[]

Inject the io_openliberty_guides_inventory_inMaintenance property, and add the isInMaintenance() class method.

The @Inject and @ConfigProperty annotations inject the io_openliberty_guides_inventory_inMaintenance configuration property from the CustomConfigSource.json file. The Provider<> interface used, forces the service to retrieve the inMaintenance value just in time. This retrieval of the value just in time makes the config injection dynamic and able to change without having to restart the application.

Every time that you invoke the inMaintenance.get() method, the Provider<> interface picks up the latest value of the io_openliberty_guides_inventory_inMaintenance property from configuration sources.

CustomConfigSource.json

1{
2  "config_ordinal": 150,
3  "io_openliberty_guides_email": "[email protected]",
4  "io_openliberty_guides_inventory_inMaintenance": false,
5  "io_openliberty_guides_system_inMaintenance": false,
6  "io_openliberty_guides_testConfigOverwrite": "CustomSource"
7}

Creating custom converters

Configuration values are purely Strings. MicroProfile Config API has built-in converters that automatically converts configured Strings into target types such as int, Integer, boolean, Boolean, float, Float, double and Double. Therefore, in the previous section, it is type-safe to directly set the variable type to Provider<Boolean>.

To convert configured Strings to an arbitrary class type, such as the Email class type,

Replace the Email Class.
src/main/java/io/openliberty/guides/config/Email.java

Email.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13
14package io.openliberty.guides.config;
15
16// tag::email[]
17public class Email {
18  private String name;
19  private String domain;
20
21  public Email(String value) {
22    String[] components = value.split("@");
23    if (components.length == 2) {
24      name = components[0];
25      domain = components[1];
26    }
27  }
28
29  public String getEmailName() {
30    return name;
31  }
32
33  public String getEmailDomain() {
34    return domain;
35  }
36
37  public String toString() {
38    return name + "@" + domain;
39  }
40}
41// end::email[]

To use this Email class type, add a custom converter by implementing the generic interface org.eclipse.microprofile.config.spi.Converter<T>. The Type parameter of the interface is the target type the String is converted to.

Create the CustomEmailConverter class.
src/main/java/io/openliberty/guides/config/CustomEmailConverter.java

CustomEmailConverter.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2019 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.config;
14
15import org.eclipse.microprofile.config.spi.Converter;
16import io.openliberty.guides.config.Email;
17
18// tag::customConfig[]
19public class CustomEmailConverter implements Converter<Email> {
20
21  @Override
22  public Email convert(String value) {
23    return new Email(value);
24  }
25
26}
27// end::customConfig[]

This implements the Converter<T> interface.

To register your implementation,

Create the configuration file.
src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter

org.eclipse.microprofile.config.spi.Converter

1io.openliberty.guides.config.CustomEmailConverter

Add the fully qualified class name of the custom converter into it.

To use the custom Email converter,

Replace the InventoryConfig class.
src/main/java/io/openliberty/guides/inventory/InventoryConfig.java

InventoryConfig.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::config-class[]
14package io.openliberty.guides.inventory;
15
16import jakarta.enterprise.context.RequestScoped;
17import jakarta.inject.Inject;
18import jakarta.inject.Provider;
19import org.eclipse.microprofile.config.inject.ConfigProperty;
20import io.openliberty.guides.config.Email;
21
22@RequestScoped
23public class InventoryConfig {
24
25  // tag::port-number[]
26  // tag::inject-port-number[]
27  @Inject
28  // end::inject-port-number[]
29  // tag::guides-port-number[]
30  @ConfigProperty(name = "io_openliberty_guides_port_number")
31  // end::guides-port-number[]
32  private int portNumber;
33
34  // end::port-number[]
35  // tag::build-in-converter[]
36  // tag::inject-inMaintenance[]
37  // tag::inject[]
38  @Inject
39  // end::inject[]
40  // tag::configPropety[]
41  @ConfigProperty(name = "io_openliberty_guides_inventory_inMaintenance")
42  // end::configPropety[]
43  // end::inject-inMaintenance[]
44  private Provider<Boolean> inMaintenance;
45
46  // end::build-in-converter[]
47  // tag::custom-converter[]
48  @Inject
49  @ConfigProperty(name = "io_openliberty_guides_email")
50  private Provider<Email> email;
51  // end::custom-converter[]
52
53  // tag::getPortNumber[]
54  public int getPortNumber() {
55    return portNumber;
56  }
57  // end::getPortNumber[]
58
59  // tag::isInMaintenance[]
60  public boolean isInMaintenance() {
61    // tag::inMaintenanceGet[]
62    return inMaintenance.get();
63    // end::inMaintenanceGet[]
64  }
65  // end::isInMaintenance[]
66
67  // tag::getEmail[]
68  public Email getEmail() {
69    return email.get();
70  }
71  // end::getEmail[]
72}
73// end::config-class[]

Inject the io_openliberty_guides_email property, and add the getEmail() method.

Adding configuration to the microservice

To use externalized configuration in the inventory service,

Replace the InventoryResource class.
src/main/java/io/openliberty/guides/inventory/InventoryResource.java

InventoryResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2017, 2022 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13// tag::config-methods[]
14package io.openliberty.guides.inventory;
15
16import java.util.Properties;
17
18// CDI
19import jakarta.enterprise.context.RequestScoped;
20import jakarta.inject.Inject;
21import jakarta.ws.rs.GET;
22import jakarta.ws.rs.Path;
23import jakarta.ws.rs.PathParam;
24import jakarta.ws.rs.Produces;
25import jakarta.ws.rs.core.MediaType;
26import jakarta.ws.rs.core.Response;
27
28
29
30@RequestScoped
31@Path("systems")
32public class InventoryResource {
33
34  @Inject
35  InventoryManager manager;
36
37  // tag::config-injection[]
38  @Inject
39  InventoryConfig inventoryConfig;
40  // end::config-injection[]
41
42  @GET
43  @Path("{hostname}")
44  @Produces(MediaType.APPLICATION_JSON)
45  public Response getPropertiesForHost(@PathParam("hostname") String hostname) {
46
47    if (!inventoryConfig.isInMaintenance()) {
48      // tag::config-port[]
49      Properties props = manager.get(hostname, inventoryConfig.getPortNumber());
50      // end::config-port[]
51      if (props == null) {
52        return Response.status(Response.Status.NOT_FOUND)
53                       .entity("{ \"error\" : \"Unknown hostname or the system service "
54                       + "may not be running on " + hostname + "\" }")
55                       .build();
56      }
57
58      // Add to inventory
59      manager.add(hostname, props);
60      return Response.ok(props).build();
61    } else {
62      // tag::email[]
63      return Response.status(Response.Status.SERVICE_UNAVAILABLE)
64                     .entity("{ \"error\" : \"Service is currently in maintenance. "
65                     + "Contact: " + inventoryConfig.getEmail().toString() + "\" }")
66                     .build();
67      // end::email[]
68    }
69  }
70
71  @GET
72  @Produces(MediaType.APPLICATION_JSON)
73  public Response listContents() {
74    // tag::isInMaintenance[]
75    if (!inventoryConfig.isInMaintenance()) {
76    // end::isInMaintenance[]
77      return Response.ok(manager.list()).build();
78    } else {
79      // tag::email[]
80      return Response.status(Response.Status.SERVICE_UNAVAILABLE)
81                     .entity("{ \"error\" : \"Service is currently in maintenance. "
82                     + "Contact: " + inventoryConfig.getEmail().toString() + "\" }")
83                     .build();
84      // end::getEmail[]
85    }
86  }
87
88}
89
90// end::config-methods[]

To add configuration to the inventory service, the InventoryConfig object is injected to the existing class.

The port number from the configuration is retrieved by the inventoryConfig.getPortNumber() method and passed to the manager.get() method as a parameter.

To determine whether the inventory service is in maintenance or not (according to the configuration value), inventoryConfig.isInMaintenance() class method is used. If you set the io_openliberty_guides_inventory_inMaintenance property to true in the configuration, the inventory service returns the message, ERROR: Service is currently in maintenance, along with the contact email. The email configuration value can be obtained by calling inventoryConfig.getEmail() method.

CustomConfigSource.json

1{
2  "config_ordinal": 150,
3  "io_openliberty_guides_email": "[email protected]",
4  "io_openliberty_guides_inventory_inMaintenance": false,
5  "io_openliberty_guides_system_inMaintenance": false,
6  "io_openliberty_guides_testConfigOverwrite": "CustomSource"
7}

Running the application

You started the Open Liberty server in dev mode at the beginning of the guide, so all the changes were automatically picked up.

While the server is running, the following two microservices should be available to access:

You can find the service that retrieves configuration information that is specific to this guide at the following location:

The config_ordinal value of the custom configuration source is set to 150. It overrides configuration values of the default microprofile-config.properties source, which has a config_ordinal value of 100.

CustomConfigSource.json

1{
2  "config_ordinal": 150,
3  "io_openliberty_guides_email": "[email protected]",
4  "io_openliberty_guides_inventory_inMaintenance": false,
5  "io_openliberty_guides_system_inMaintenance": false,
6  "io_openliberty_guides_testConfigOverwrite": "CustomSource"
7}

microprofile-config.properties

 1# tag::all-props[]
 2# tag::ordinal[]
 3config_ordinal=100
 4# end::ordinal[]
 5# tag::inventory-port-number[]
 6io_openliberty_guides_port_number=9080
 7# end::inventory-port-number[]
 8# tag::inventory-in-maintenance[]
 9io_openliberty_guides_inventory_inMaintenance=false
10# end::inventory-in-maintenance[]
11# tag::system-in-maintenance[]
12io_openliberty_guides_system_inMaintenance=false
13# tag::system-in-maintenance[]
14# tag::test-config-overwrite[]
15io_openliberty_guides_testConfigOverwrite=DefaultSource
16# tag::test-config-overwrite[]
17# end::all-props[]

Play with this application by changing configuration values for each property in the resources/CustomConfigSource.json file. Your changes are added dynamically, and you do not need to restart the server. Refresh http://localhost:9080/config to see the dynamic changes.

For example, change io_openliberty_guides_inventory_inMaintenance from false to true, then try to access http://localhost:9080/inventory/systems again. The following message displays: ERROR: Service is currently in maintenance.

Testing the application

Create the ConfigurationIT class.
src/test/java/it/io/openliberty/guides/config/ConfigurationIT.java

ConfigurationIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2017, 2022 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13// tag::test[]
 14package it.io.openliberty.guides.config;
 15
 16import static org.junit.jupiter.api.Assertions.assertEquals;
 17
 18import jakarta.ws.rs.client.Client;
 19import jakarta.ws.rs.client.ClientBuilder;
 20import jakarta.ws.rs.core.Response;
 21
 22import org.junit.jupiter.api.AfterEach;
 23import org.junit.jupiter.api.BeforeEach;
 24import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
 25import org.junit.jupiter.api.Order;
 26import org.junit.jupiter.api.Test;
 27import org.junit.jupiter.api.TestMethodOrder;
 28
 29@TestMethodOrder(OrderAnnotation.class)
 30public class ConfigurationIT {
 31
 32  private String port;
 33  private String baseUrl;
 34  private Client client;
 35
 36  private final String INVENTORY_HOSTS = "inventory/systems";
 37  private final String USER_DIR = System.getProperty("user.dir");
 38  private final String DEFAULT_CONFIG_FILE = USER_DIR
 39      + "/src/main/resources/META-INF/microprofile-config.properties";
 40  private final String CUSTOM_CONFIG_FILE = USER_DIR.split("target")[0]
 41      + "/resources/CustomConfigSource.json";
 42  private final String INV_MAINTENANCE_PROP = "io_openliberty_guides"
 43      + "_inventory_inMaintenance";
 44
 45  @BeforeEach
 46  // tag::setup[]
 47  public void setup() {
 48    port = System.getProperty("default.http.port");
 49    baseUrl = "http://localhost:" + port + "/";
 50    ConfigITUtil.setDefaultJsonFile(CUSTOM_CONFIG_FILE);
 51
 52    client = ClientBuilder.newClient();
 53  }
 54  // end::setup[]
 55
 56  @AfterEach
 57  public void teardown() {
 58    ConfigITUtil.setDefaultJsonFile(CUSTOM_CONFIG_FILE);
 59    client.close();
 60  }
 61
 62  @Test
 63  @Order(1)
 64  // tag::testInitialServiceStatus[]
 65  public void testInitialServiceStatus() {
 66    boolean status = Boolean.valueOf(ConfigITUtil.readPropertyValueInFile(
 67        INV_MAINTENANCE_PROP, DEFAULT_CONFIG_FILE));
 68    if (!status) {
 69      Response response = ConfigITUtil.getResponse(client, baseUrl + INVENTORY_HOSTS);
 70
 71      int expected = Response.Status.OK.getStatusCode();
 72      int actual = response.getStatus();
 73      assertEquals(expected, actual);
 74    } else {
 75      assertEquals(
 76         "{ \"error\" : \"Service is currently in maintenance."
 77         + "Contact: [email protected]\" }",
 78          ConfigITUtil.getStringFromURL(client, baseUrl + INVENTORY_HOSTS),
 79          "The Inventory Service should be in maintenance");
 80    }
 81  }
 82  // end::testInitialServiceStatus[]
 83
 84  @Test
 85  @Order(2)
 86  // tag::testPutServiceInMaintenance[]
 87  public void testPutServiceInMaintenance() {
 88    Response response = ConfigITUtil.getResponse(client, baseUrl + INVENTORY_HOSTS);
 89
 90    int expected = Response.Status.OK.getStatusCode();
 91    int actual = response.getStatus();
 92    assertEquals(expected, actual);
 93
 94    ConfigITUtil.switchInventoryMaintenance(CUSTOM_CONFIG_FILE, true);
 95
 96    String error = ConfigITUtil.getStringFromURL(client, baseUrl + INVENTORY_HOSTS);
 97
 98    assertEquals(
 99         "{ \"error\" : \"Service is currently in maintenance. "
100         + "Contact: [email protected]\" }",
101        error, "The inventory service should be down in the end");
102  }
103  // end::testPutServiceInMaintenance[]
104
105  @Test
106  @Order(3)
107  // tag::testChangeEmail[]
108  public void testChangeEmail() {
109    ConfigITUtil.switchInventoryMaintenance(CUSTOM_CONFIG_FILE, true);
110
111    String error = ConfigITUtil.getStringFromURL(client, baseUrl + INVENTORY_HOSTS);
112
113    assertEquals(
114         "{ \"error\" : \"Service is currently in maintenance. "
115         + "Contact: [email protected]\" }",
116        error, "The email should be [email protected] in the beginning");
117
118    ConfigITUtil.changeEmail(CUSTOM_CONFIG_FILE, "[email protected]");
119
120    error = ConfigITUtil.getStringFromURL(client, baseUrl + INVENTORY_HOSTS);
121
122    assertEquals(
123         "{ \"error\" : \"Service is currently in maintenance. "
124         + "Contact: [email protected]\" }",
125        error, "The email should be [email protected] in the beginning");
126  }
127  // end::testChangeEmail[]
128
129}
130// end::test[]

microprofile-config.properties

 1# tag::all-props[]
 2# tag::ordinal[]
 3config_ordinal=100
 4# end::ordinal[]
 5# tag::inventory-port-number[]
 6io_openliberty_guides_port_number=9080
 7# end::inventory-port-number[]
 8# tag::inventory-in-maintenance[]
 9io_openliberty_guides_inventory_inMaintenance=false
10# end::inventory-in-maintenance[]
11# tag::system-in-maintenance[]
12io_openliberty_guides_system_inMaintenance=false
13# tag::system-in-maintenance[]
14# tag::test-config-overwrite[]
15io_openliberty_guides_testConfigOverwrite=DefaultSource
16# tag::test-config-overwrite[]
17# end::all-props[]

The testInitialServiceStatus() test case reads the value of the io_openliberty_guides_inventory_inMaintenance configuration property in the META-INF/microprofile-config.properties file and checks the HTTP response of the inventory service. If the configuration value is false, the service returns a valid response. Otherwise, the service returns the following message: ERROR: Service is currently in maintenance.

Because the io_openliberty_guides_inventory_inMaintenance configuration property is set to false by default, the testPutServiceInMaintenance() test case first checks that the inventory service is not in maintenance in the beginning. Next, this test switches the value of the io_openliberty_guides_inventory_inMaintenance configuration property to true. In the end, the inventory service returns the following message: ERROR: Service is currently in maintenance.

The testChangeEmail() test case first puts the inventory service in maintenance, then it changes the email address in the configuration file. In the end, the inventory service should display the error message with the latest email address.

In addition, a few endpoint tests have been provided for you to test the basic functionality of the inventory and system services. If a test failure occurs, then you must have introduced a bug into the code. Remember that you must register the custom configuration source and custom converter in the src/main/resources/META-INF/services/ directory. If you don’t complete these steps, the tests will fail. These tests run automatically as a part of the integration test suite.

Running the tests

Because you started Open Liberty in dev mode, you can run the tests by pressing the enter/return key from the command-line session where you started dev mode.

You see the following output:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.config.ConfigurationIT
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.92 s - in it.io.openliberty.guides.config.ConfigurationIT
Running it.io.openliberty.guides.system.SystemEndpointIT
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.017 s - in it.io.openliberty.guides.system.SystemEndpointIT
Running it.io.openliberty.guides.inventory.InventoryEndpointIT
[WARNING ] Interceptor for {http://client.inventory.guides.openliberty.io/}SystemClient has thrown exception, unwinding now
Could not send Message.
[err] The specified host is unknown.
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.077 s - in it.io.openliberty.guides.inventory.InventoryEndpointIT

Results:

Tests run: 7, Failures: 0, Errors: 0, Skipped: 0

The warning and error messages are expected and result from a request to a bad or an unknown hostname. This request is made in the testUnknownHost() test from the InventoryEndpointIT integration test.

To see whether the tests detect a failure, remove the configuration resetting line in the setup() method of the ConfigurationIT.java file. Then, manually change some configuration values in the resources/CustomConfigSource.json file. Rerun the tests. You will see a test failure occur.

When you are done checking out the service, exit dev mode by pressing CTRL+C in the command-line session where you ran the server, or by typing q and then pressing the enter/return key.

Great work! You’re done!

You just built and tested a MicroProfile application with MicroProfile Config in Open Liberty.

Feel free to try one of the related guides. They demonstrate new technologies that you can learn and expand on top what you built in this guide.

Guide Attribution

Configuring microservices by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star