Optimizing configuration for microservices

duration 25 minutes

Prerequisites:

Learn how to optimize configuration management for microservices with advanced features like property expressions, retrieval of multi-valued properties as lists, configuration source APIs, and bulk-extraction of configuration properties by using MicroProfile Config.

What you’ll learn

You’ll learn how to improve flexibility and maintainability of your application’s configurations by using MicroProfile Config.

MicroProfile Config streamlines the configuration process for microservices by providing a flexible mechanism for application configuration. Applications can use the MicroProfile Config API to eliminate the need for repetitive injection statements and simplify the handling of complex configurations with bulk-extraction of configuration properties, dynamic property expressions, and retrieval of multi-valued properties as lists. Additionally, the configuration source APIs allow for fine-grained control and access to the metadata and values of configuration properties, enabling greater flexibility and customization in managing application configurations.

This guide builds on the basics of the Separating configuration from code in microservices guide and the Configuring microservices guide. If you are not familiar with externalizing the configuration of microservices, it will be helpful to read this document and complete those guides before proceeding.

The application that you will be working with is a query service, which fetches information about the running JVM from a system microservice. You will use MicroProfile Config to manage configurations for the query service.

You will learn how to use property expressions in property values and how to retrieve property as a list by using the @ConfigProperty annotation. You will also learn how to use the configuration source APIs to retrieve information about a single specified configuration property or the metadata of the underlying application configuration. Finally, you will learn how to aggregate configuration properties into a CDI bean and inject it using the @ConfigProperties annotation.

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-apis.git
cd guide-microprofile-config-apis

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 contains the finished implementation for the services in the application. Try the finished application before you build your own.

To try out the application, run the following commands to navigate to the finish/system directory and deploy the system service to Open Liberty:

cd finish/system
mvn liberty:run

Open another command-line session and run the following commands to navigate to the finish/query directory and deploy the query service to Open Liberty:

cd finish/query
mvn liberty:run

After you see the following message in both command-line sessions, both of your services are ready:

The defaultServer server is ready to run a smarter planet.

Point your browser to the following URLs:

After you finish checking out the application, leave the system service running and stop the query service by pressing CTRL+C in the command-line session where you ran the query service. Alternatively, you can run the following goal from the finish/query directory in another command-line session:

mvn liberty:stop

Defining a property with expressions

In MicroProfile Config, property expressions provide a way to set and expand variables in property values. This is useful when the value of one property depends on the value of another property. For example, defining a server.baseURL property as http://${server.host}:${server.port} would automatically update the value of server.baseURL when the values of server.host or server.port change. To use property expressions, define a property with a ${expression} syntax, where expression is the name of another configuration property. When the property is loaded, the ${expression} syntax is replaced with the value of the referenced property.

You can learn more about property expressions and their syntax at the MicroProfile Config specification.

Navigate to the start directory to begin.

When you run Open Liberty in dev mode, dev mode listens for file changes and automatically recompiles and deploys your updates whenever you save a new change. Run the following commands to navigate to the query directory and start the query service in dev mode:

cd query
mvn liberty:dev

After you see the following message, your Liberty instance is ready in dev mode:

**************************************************************
*    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.

query/pom.xml

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5    <modelVersion>4.0.0</modelVersion>
 6
 7    <groupId>io.openliberty.guides</groupId>
 8    <artifactId>guide-microprofile-config-profile-query</artifactId>
 9    <version>1.0-SNAPSHOT</version>
10    <packaging>war</packaging>
11
12    <properties>
13        <maven.compiler.source>11</maven.compiler.source>
14        <maven.compiler.target>11</maven.compiler.target>
15        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16        <liberty.var.default.http.port>9085</liberty.var.default.http.port>
17        <liberty.var.default.https.port>9448</liberty.var.default.https.port>
18    </properties>
19
20    <dependencies>
21        <dependency>
22            <groupId>jakarta.platform</groupId>
23            <artifactId>jakarta.jakartaee-api</artifactId>
24            <version>10.0.0</version>
25            <scope>provided</scope>
26        </dependency>
27        <!-- tag::mpconfig[] -->
28        <dependency>
29            <groupId>org.eclipse.microprofile</groupId>
30            <artifactId>microprofile</artifactId>
31            <version>6.0</version>
32            <type>pom</type>
33            <scope>provided</scope>
34        </dependency>
35        <!-- end::mpconfig[] -->
36
37        <!-- For tests -->
38        <dependency>
39            <groupId>org.junit.jupiter</groupId>
40            <artifactId>junit-jupiter</artifactId>
41            <version>5.9.2</version>
42            <scope>test</scope>
43        </dependency>
44        <dependency>
45            <groupId>org.jboss.resteasy</groupId>
46            <artifactId>resteasy-client</artifactId>
47            <version>6.2.3.Final</version>
48            <scope>test</scope>
49        </dependency>
50        <dependency>
51            <groupId>org.jboss.resteasy</groupId>
52            <artifactId>resteasy-json-binding-provider</artifactId>
53            <version>6.2.3.Final</version>
54            <scope>test</scope>
55        </dependency>
56    </dependencies>
57
58    <build>
59        <finalName>${project.artifactId}</finalName>
60        <plugins>
61            <plugin>
62                <groupId>org.apache.maven.plugins</groupId>
63                <artifactId>maven-war-plugin</artifactId>
64                <version>3.3.2</version>
65            </plugin>
66            <plugin>
67                <groupId>io.openliberty.tools</groupId>
68                <artifactId>liberty-maven-plugin</artifactId>
69                <version>3.8.2</version>
70            </plugin>
71            <plugin>
72                <groupId>org.apache.maven.plugins</groupId>
73                <artifactId>maven-failsafe-plugin</artifactId>
74                <version>3.0.0</version>
75                <configuration>
76                    <!-- tag::systemPropertyVariables[] -->
77                    <systemPropertyVariables>
78                        <http.port>${liberty.var.default.http.port}</http.port>
79                        <system.host>localhost</system.host>
80                    </systemPropertyVariables>
81                    <!-- end::systemPropertyVariables[] -->
82                </configuration>
83            </plugin>
84        </plugins>
85    </build>
86</project>

query/server.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<server description="query service">
 3
 4    <featureManager>
 5        <feature>restfulWS-3.1</feature>
 6        <feature>jsonb-3.0</feature>
 7        <feature>jsonp-2.1</feature>
 8        <feature>cdi-4.0</feature>
 9        <!-- tag::mpconfig[] -->
10        <feature>mpConfig-3.0</feature>
11        <!-- end::mpconfig[] -->
12        <feature>mpRestClient-3.0</feature>
13    </featureManager>
14
15    <variable name="default.http.port" defaultValue="9085"/>
16    <variable name="default.https.port" defaultValue="9448"/>
17
18    <httpEndpoint id="defaultHttpEndpoint"
19        host="*"
20        httpPort="${default.http.port}" 
21        httpsPort="${default.https.port}"/>
22
23    <webApplication contextRoot="/query"
24                    location="guide-microprofile-config-profile-query.war"/>
25
26</server>

The MicroProfile Config API is included in the MicroProfile dependency that is specified in your query/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 query/src/main/liberty/config/server.xml configuration file.

Replace the microprofile-config.properties file.
query/src/main/resources/META-INF/microprofile-config.properties

query/microprofile-config.properties

 1# tag::system[]
 2system.httpPort=9081
 3# tag::user[]
 4system.user=alice
 5# end::user[]
 6# tag::password[]
 7system.password=alicepwd
 8# end::password[]
 9# tag::userPassword[]
10system.userPassword=${system.user}:${system.password}
11# end::userPassword[]
12system.contextRoot=system/dev
13# end::system[]
14# tag::properties[]
15system.properties=os.name,os.arch,java.version,java.vendor
16# end::properties[]
17
18# tag::roleAndQuery[]
19# tag::role[]
20role=developer
21# end::role[]
22# tag::query[]
23query.tester=bob
24query.developer=alice
25# tag::contactEmail[]
26query.contactEmail=${query.${role}:admin}@ol.guides.com
27# end::contactEmail[]
28# end::query[]
29# end::roleAndQuery[]

Define a property called system.userPassword that references two other properties, system.user and system.password.

The ${system.user} and ${system.password} expressions are replaced with their respective values when the system.userPassword property is loaded, resulting in alice:alicepwd.

To use this configuration property, add it to the SystemResource class.

Replace the SystemResource class.
query/src/main/java/io/openliberty/guides/query/SystemResource.java

query/SystemResource.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2023 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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.query;
13
14import java.net.URI;
15import java.util.Base64;
16import java.util.List;
17import java.util.Properties;
18
19import org.eclipse.microprofile.config.inject.ConfigProperty;
20import org.eclipse.microprofile.rest.client.RestClientBuilder;
21
22import io.openliberty.guides.query.client.SystemClient;
23import io.openliberty.guides.query.client.UnknownUriExceptionMapper;
24import jakarta.enterprise.context.ApplicationScoped;
25import jakarta.inject.Inject;
26import jakarta.ws.rs.Consumes;
27import jakarta.ws.rs.GET;
28import jakarta.ws.rs.Path;
29import jakarta.ws.rs.PathParam;
30import jakarta.ws.rs.Produces;
31import jakarta.ws.rs.core.MediaType;
32
33@ApplicationScoped
34@Path("/systems")
35public class SystemResource {
36
37    @Inject
38    @ConfigProperty(name = "system.httpPort")
39    private String systemHttpPort;
40
41    // tag::userPassword[]
42    @Inject
43    @ConfigProperty(name = "system.userPassword")
44    private String systemUserPassword;
45    // end::userPassword[]
46
47    @Inject
48    @ConfigProperty(name = "system.contextRoot")
49    private String systemContextRoot;
50
51    @GET
52    @Path("/{hostname}")
53    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
54    @Produces(MediaType.APPLICATION_JSON)
55    public Properties getSystemProperties(@PathParam("hostname") String hostname) {
56
57        SystemClient systemClient = null;
58        Properties p = new Properties();
59
60        try {
61            String uriString = "http://" + hostname + ":" + systemHttpPort
62                               + "/" + systemContextRoot;
63            URI customURI = URI.create(uriString);
64            systemClient = RestClientBuilder.newBuilder()
65                .baseUri(customURI)
66                .register(UnknownUriExceptionMapper.class)
67                .build(SystemClient.class);
68        } catch (Exception e) {
69            p.put("fail", "Failed to create the client " + hostname + ".");
70            return p;
71        }
72
73        // tag::authHeader[]
74        String authHeader = "Basic "
75               + Base64.getEncoder().encodeToString(systemUserPassword.getBytes());
76        // end::authHeader[]
77
78        try {
79            p.put("os.name", systemClient.getProperty(authHeader, "os.name"));
80            p.put("java.version", systemClient.getProperty(authHeader, "java.version"));
81        } catch (Exception e) {
82            p.put("fail", "Failed to reach the client " + hostname + ".");
83            return p;
84        } finally {
85            try {
86                systemClient.close();
87            } catch (Exception e) {
88                e.printStackTrace();
89            }
90        }
91        return p;
92    }
93
94}

system/server.xml

 1<server description="system service">
 2
 3    <featureManager>
 4        <feature>restfulWS-3.1</feature>
 5        <feature>jsonb-3.0</feature>
 6        <feature>jsonp-2.1</feature>
 7        <feature>cdi-4.0</feature>
 8        <feature>mpConfig-3.0</feature>
 9        <feature>appSecurity-5.0</feature>
10    </featureManager>
11
12    <!-- tag::defaultConfig[] -->
13    <!-- tag::port[] -->
14    <variable name="default.http.port" defaultValue="9080"/>
15    <!-- end::port[] -->
16    <variable name="default.https.port" defaultValue="9443"/>
17    <!-- tag::username[] -->
18    <variable name="default.username" defaultValue="admin"/>
19    <!-- end::username[] -->
20    <!-- tag::password[] -->
21    <variable name="default.password" defaultValue="adminpwd"/>
22    <!-- end::password[] -->
23    <!-- tag::context.root[] -->
24    <variable name="context.root" defaultValue="system"/>
25    <!-- end::context.root[] -->
26    <!-- end::defaultConfig[] -->
27
28    <!-- tag::httpEndpoint[] -->
29    <httpEndpoint id="defaultHttpEndpoint" host="*"
30        httpPort="${default.http.port}" 
31        httpsPort="${default.https.port}"/>
32    <!-- end::httpEndpoint[] -->
33
34    <!-- tag::webApplication[] -->
35    <webApplication location="guide-microprofile-config-profile-system.war"
36        contextRoot="${context.root}"/>
37    <!-- end::webApplication[] -->
38
39           <!-- tag::basicRegistry[] -->
40    <basicRegistry id="basic" realm="BasicRegistry">
41        <user name="${default.username}" password="${default.password}"/>
42    </basicRegistry>
43    <!-- end::basicRegistry[] -->
44
45</server>

Injects the system.userPassword property instead of the separate system.user and system.password properties. Then, use the systemUserPassword variable to construct the authorization header for the system service.

Note that only registered users can access the system microservice. To authenticate a user, a basicRegistry element is configured in the system Liberty configuration file that is at system/src/main/liberty/config/server.xml. This basicRegistry element is a simple case for learning purposes, and for more information about different user registries, see the User registries documentation.

Because you are running the query service in dev mode, the changes that you made were automatically picked up. You’re now ready to check out your application in your browser.

Point your browser to the http://localhost:9085/query/systems/localhost URL. You can see the current OS name and Java version in JSON format.

Injecting property as a list

Configuration values are just Strings. MicroProfile Config API has built-in converters that automatically convert configured Strings into target types. When injecting a multi-valued property, MicroProfile Config API assumes that the property value is a comma-separated list of items. It then splits the value into individual items and returns them as a List of Strings.

Retrieving a configuration property as a list can be beneficial in situations where the configuration property contains multiple values that need to be accessed and processed within the application. For example, imagine an application that needs to connect to multiple databases. By defining these values as a list of items in the ConfigSource and retrieving them as a list in the application, you can easily access and process each value without having to manually split the values into separate variables.

Replace the microprofile-config.properties file.
query/src/main/resources/META-INF/microprofile-config.properties

query/microprofile-config.properties

 1# tag::system[]
 2system.httpPort=9081
 3# tag::user[]
 4system.user=alice
 5# end::user[]
 6# tag::password[]
 7system.password=alicepwd
 8# end::password[]
 9# tag::userPassword[]
10system.userPassword=${system.user}:${system.password}
11# end::userPassword[]
12system.contextRoot=system/dev
13# end::system[]
14# tag::properties[]
15system.properties=os.name,os.arch,java.version,java.vendor
16# end::properties[]
17
18# tag::roleAndQuery[]
19# tag::role[]
20role=developer
21# end::role[]
22# tag::query[]
23query.tester=bob
24query.developer=alice
25# tag::contactEmail[]
26query.contactEmail=${query.${role}:admin}@ol.guides.com
27# end::contactEmail[]
28# end::query[]
29# end::roleAndQuery[]

Define the system.properties property as a comma-separated list of system properties.

To use this configuration property,

Replace the SystemResource class.
query/src/main/java/io/openliberty/guides/query/SystemResource.java

query/SystemResource.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2023 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 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package io.openliberty.guides.query;
 13
 14import java.net.URI;
 15import java.util.Base64;
 16import java.util.List;
 17import java.util.Properties;
 18
 19import org.eclipse.microprofile.config.inject.ConfigProperty;
 20import org.eclipse.microprofile.rest.client.RestClientBuilder;
 21
 22import io.openliberty.guides.query.client.SystemClient;
 23import io.openliberty.guides.query.client.UnknownUriExceptionMapper;
 24import jakarta.enterprise.context.ApplicationScoped;
 25import jakarta.inject.Inject;
 26import jakarta.ws.rs.Consumes;
 27import jakarta.ws.rs.GET;
 28import jakarta.ws.rs.Path;
 29import jakarta.ws.rs.PathParam;
 30import jakarta.ws.rs.Produces;
 31import jakarta.ws.rs.core.MediaType;
 32
 33@ApplicationScoped
 34@Path("/systems")
 35public class SystemResource {
 36
 37    @Inject
 38    @ConfigProperty(name = "system.httpPort")
 39    private String systemHttpPort;
 40
 41    @Inject
 42    @ConfigProperty(name = "system.userPassword")
 43    private String systemUserPassword;
 44
 45    @Inject
 46    @ConfigProperty(name = "system.contextRoot")
 47    private String systemContextRoot;
 48
 49    // tag::systemPropertiesInject[]
 50    @Inject
 51    // end::systemPropertiesInject[]
 52    // tag::systemPropertiesProperty[]
 53    @ConfigProperty(name = "system.properties")
 54    // end::systemPropertiesProperty[]
 55    // tag::listStringType[]
 56    private List<String> systemProperties;
 57    // end::listStringType[]
 58
 59    @GET
 60    @Path("/{hostname}")
 61    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
 62    @Produces(MediaType.APPLICATION_JSON)
 63    // tag::getSystemPropertiesMethod[]
 64    public Properties getSystemProperties(@PathParam("hostname") String hostname) {
 65
 66        SystemClient systemClient = null;
 67        Properties p = new Properties();
 68
 69        try {
 70            String uriString = "http://" + hostname + ":" + systemHttpPort
 71                               + "/" + systemContextRoot;
 72            URI customURI = URI.create(uriString);
 73            systemClient = RestClientBuilder.newBuilder()
 74                .baseUri(customURI)
 75                .register(UnknownUriExceptionMapper.class)
 76                .build(SystemClient.class);
 77        } catch (Exception e) {
 78            p.put("fail", "Failed to create the client " + hostname + ".");
 79            return p;
 80        }
 81
 82        String authHeader = "Basic "
 83               + Base64.getEncoder().encodeToString(systemUserPassword.getBytes());
 84
 85        try {
 86            // tag::systemProperties[]
 87            for (String property : systemProperties) {
 88                p.put(property, systemClient.getProperty(authHeader, property));
 89            }
 90            // end::systemProperties[]
 91        } catch (Exception e) {
 92            p.put("fail", "Failed to reach the client " + hostname + ".");
 93            return p;
 94        } finally {
 95            try {
 96                systemClient.close();
 97            } catch (Exception e) {
 98                e.printStackTrace();
 99            }
100        }
101        return p;
102    }
103    // end::getSystemPropertiesMethod[]
104
105}

Inject the system.properties property by using the @Inject and @ConfigProperty annotations with a variable type of List<String>.

The systemProperties variable retrieves the list of system properties from the configuration and passes it to the getSystemProperties() method.

The getSystemProperties() then iterates through this list of system properties by using the systemProperties variable to retrieve its value from the system service.

Point your browser to the http://localhost:9085/query/systems/localhost URL. You can see the list of system properties that are retrieved from the system.properties property in JSON format.

Using configuration source APIs

MicroProfile Config provides configuration source APIs for obtaining detailed information about your application configuration. These APIs can be used to retrieve various useful information about a single specified configuration property by using the ConfigValue API class, or to look up the metadata of all underlying configurations of your microservice by using the Config API class.

The configuration source APIs offer a powerful toolset for managing and utilizing your microservice configuration in a flexible and efficient manner. They enable you to add configurations from various sources, such as the server.xml, server.env, and bootstrap.properties files, or ConfigSources for different environments, such as development, testing, and production. With these APIs, you can easily determine which sources and properties are loaded in your current environment and make changes accordingly. These APIs can also be useful for debugging and troubleshooting purposes.

To explore the capabilities of these APIs, you will first add more configuration properties to the ConfigSource.

Replace the microprofile-config.properties file.
query/src/main/resources/META-INF/microprofile-config.properties

query/microprofile-config.properties

 1# tag::system[]
 2system.httpPort=9081
 3# tag::user[]
 4system.user=alice
 5# end::user[]
 6# tag::password[]
 7system.password=alicepwd
 8# end::password[]
 9# tag::userPassword[]
10system.userPassword=${system.user}:${system.password}
11# end::userPassword[]
12system.contextRoot=system/dev
13# end::system[]
14# tag::properties[]
15system.properties=os.name,os.arch,java.version,java.vendor
16# end::properties[]
17
18# tag::roleAndQuery[]
19# tag::role[]
20role=developer
21# end::role[]
22# tag::query[]
23query.tester=bob
24query.developer=alice
25# tag::contactEmail[]
26query.contactEmail=${query.${role}:admin}@ol.guides.com
27# end::contactEmail[]
28# end::query[]
29# end::roleAndQuery[]

Add the role and query.* properties to the microprofile-config.properties file.

Note that the query.contactEmail property contains a composed property expression where the ${role} inner expression is expanded first. The ${query.${role}} expression provides the admin default value if the expression doesn’t find a value. In this case, the property role is defined as developer and the property query.contactEmail is expanded to alice@ol.guides.com.

To use the configuration source APIs to retrieve information about these configuration properties,

Create the ConfigResource class.
query/src/main/java/io/openliberty/guides/query/ConfigResource.java

query/ConfigResource.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2023 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 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package io.openliberty.guides.query;
 13
 14import java.util.HashMap;
 15import java.util.Map;
 16import java.util.Properties;
 17
 18import org.eclipse.microprofile.config.Config;
 19import org.eclipse.microprofile.config.ConfigValue;
 20// tag::importConfigProperties[]
 21import org.eclipse.microprofile.config.inject.ConfigProperties;
 22// end::importConfigProperties[]
 23import org.eclipse.microprofile.config.inject.ConfigProperty;
 24import org.eclipse.microprofile.config.spi.ConfigSource;
 25
 26import jakarta.enterprise.context.ApplicationScoped;
 27import jakarta.inject.Inject;
 28import jakarta.ws.rs.GET;
 29import jakarta.ws.rs.Path;
 30import jakarta.ws.rs.Produces;
 31import jakarta.ws.rs.core.MediaType;
 32
 33@ApplicationScoped
 34@Path("/config")
 35public class ConfigResource {
 36
 37    // tag::config[]
 38    @Inject
 39    private Config config;
 40    // end::config[]
 41
 42    // tag::contactEmail[]
 43    @Inject
 44    // tag::queryContactEmail[]
 45    @ConfigProperty(name = "query.contactEmail")
 46    // end::queryContactEmail[]
 47    // tag::configValue[]
 48    private ConfigValue contactConfigValue;
 49    // end::configValue[]
 50    // end::contactEmail[]
 51
 52    // tag::configSystemBean[]
 53    // tag::inject[]
 54    @Inject
 55    // end::inject[]
 56    // tag::ConfigProperties[]
 57    @ConfigProperties
 58    // end::ConfigProperties[]
 59    // tag::systemConfig[]
 60    private ConfigSystemBean systemConfig;
 61    // end::systemConfig[]
 62    // end::configSystemBean[]
 63
 64    @GET
 65    @Produces(MediaType.APPLICATION_JSON)
 66    public Map<String, Properties> getAllConfig() {
 67        Map<String, Properties> configMap = new HashMap<String, Properties>();
 68        configMap.put("ConfigSources", getConfigSources());
 69        configMap.put("ConfigProperties", getConfigProperties());
 70        return configMap;
 71    }
 72
 73    @GET
 74    @Path("/contact")
 75    @Produces(MediaType.APPLICATION_JSON)
 76    // tag::getContactConfig[]
 77    public Properties getContactConfig() {
 78        Properties configProps = new Properties();
 79        // tag::getSourceName[]
 80        String sourceName = contactConfigValue.getSourceName();
 81        // end::getSourceName[]
 82        // tag::getSourceOrdinal[]
 83        int sourceOrdinal = contactConfigValue.getSourceOrdinal();
 84        // end::getSourceOrdinal[]
 85        // tag::getValue[]
 86        String value = contactConfigValue.getValue();
 87        // end::getValue[]
 88        configProps.put("SourceName", sourceName);
 89        configProps.put("SourceOrdinal", sourceOrdinal);
 90        configProps.put("Value", value);
 91        return configProps;
 92    }
 93    // end::getContactConfig[]
 94
 95    // tag::getSystemConfig[]
 96    @GET
 97    @Path("/system")
 98    @Produces(MediaType.APPLICATION_JSON)
 99    public Properties getSystemConfig() {
100        Properties configProps = new Properties();
101        // tag::systemProperties[]
102        configProps.put("system.httpPort", systemConfig.httpPort);
103        configProps.put("system.user", systemConfig.user);
104        configProps.put("system.password", systemConfig.password);
105        configProps.put("system.userPassword", systemConfig.userPassword);
106        configProps.put("system.contextRoot", systemConfig.contextRoot);
107        configProps.put("system.properties", systemConfig.properties);
108        // end::systemProperties[]
109        return configProps;
110    }
111    // end::getSystemConfig[]
112
113    //tag::getConfigSourcesClassMethod[]
114    public Properties getConfigSources() {
115        Properties configSource = new Properties();
116        // tag::getConfigSources[]
117        for (ConfigSource source : config.getConfigSources()) {
118        // end::getConfigSources[]
119            configSource.put(source.getName(), source.getOrdinal());
120        }
121        return configSource;
122    }
123    //end::getConfigSourcesClassMethod[]
124
125    //tag::getConfigProperties[]
126    public Properties getConfigProperties() {
127        Properties configProperties = new Properties();
128        // tag::getPropertyNames[]
129        for (String name : config.getPropertyNames()) {
130        // end::getPropertyNames[]
131            if (name.startsWith("system.")
132                || name.startsWith("query.")
133                || name.equals("role")) {
134                configProperties.put(name, config.getValue(name, String.class));
135            }
136        }
137        return configProperties;
138    }
139    //end::getConfigProperties[]
140}

Inject the query.contactEmail property as a ConfigValue type, and add the getContactConfig() class method.

The ConfigValue metadata object holds additional information after the lookup of the query.contactEmail property. The getSourceName(), the getSourceOrdinal() methods determines the ConfigSource name and the ordinal value of the ConfigSource that loaded the property lookup, respectively. The getValue() methods returns the value of the property lookup.

Then, inject the Config metadata object, and add the getConfigSources() and the getConfigProperties() class methods.

The Config.getConfigSources() method in the Config API returns all the registered ConfigSources for the current service. These ConfigSources contain the configuration properties that are available to the service.

The Config.getPropertyNames() method in the Config API finds all configuration property names by searching through all the registered ConfigSources.

Now, check out the following endpoints that you just created:

Injecting configuration as a bulk through @ConfigProperties annotation

MicroProfile Config provides a practical way to aggregate related configuration properties into a single CDI bean by using the @ConfigProperties annotation. This annotation is applied to a plain old Java object (POJO) that serves as a holder for configuration properties, making it easier to retrieve. Injecting the annotated POJO as a CDI bean into a separate POJO provides a cleaner and more organized approach to managing configuration properties.

To define a CDI bean for the configuration properties that share the system. prefix,

Create the ConfigSystemBean class.
query/src/main/java/io/openliberty/guides/query/ConfigSystemBean.java

query/ConfigSystemBean.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2023 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 2.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-2.0/
 8 *
 9 * SPDX-License-Identifier: EPL-2.0
10 *******************************************************************************/
11// end::copyright[]
12package io.openliberty.guides.query;
13
14import java.util.List;
15
16import org.eclipse.microprofile.config.inject.ConfigProperties;
17
18import jakarta.enterprise.context.Dependent;
19
20// tag::prefix[]
21@ConfigProperties(prefix = "system")
22// end::prefix[]
23@Dependent
24// tag::ConfigSystemBean[]
25public class ConfigSystemBean {
26
27    public int httpPort;
28    public String user;
29    public String password;
30    public String userPassword;
31    public String contextRoot;
32    public List<String> properties;
33
34}
35// end::ConfigSystemBean[]

Annotate the ConfigSystemBean class with the @ConfigProperties annotation.

The @ConfigProperties annotation is used to specify that the ConfigSystemBean class is a holder for configuration properties. The prefix attribute of the annotation specifies the common prefix of the configuration properties, which in this case is system.

To use the ConfigSystemBean class,

Replace the ConfigResource class.
query/src/main/java/io/openliberty/guides/query/ConfigResource.java

query/ConfigResource.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2023 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 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package io.openliberty.guides.query;
 13
 14import java.util.HashMap;
 15import java.util.Map;
 16import java.util.Properties;
 17
 18import org.eclipse.microprofile.config.Config;
 19import org.eclipse.microprofile.config.ConfigValue;
 20// tag::importConfigProperties[]
 21import org.eclipse.microprofile.config.inject.ConfigProperties;
 22// end::importConfigProperties[]
 23import org.eclipse.microprofile.config.inject.ConfigProperty;
 24import org.eclipse.microprofile.config.spi.ConfigSource;
 25
 26import jakarta.enterprise.context.ApplicationScoped;
 27import jakarta.inject.Inject;
 28import jakarta.ws.rs.GET;
 29import jakarta.ws.rs.Path;
 30import jakarta.ws.rs.Produces;
 31import jakarta.ws.rs.core.MediaType;
 32
 33@ApplicationScoped
 34@Path("/config")
 35public class ConfigResource {
 36
 37    // tag::config[]
 38    @Inject
 39    private Config config;
 40    // end::config[]
 41
 42    // tag::contactEmail[]
 43    @Inject
 44    // tag::queryContactEmail[]
 45    @ConfigProperty(name = "query.contactEmail")
 46    // end::queryContactEmail[]
 47    // tag::configValue[]
 48    private ConfigValue contactConfigValue;
 49    // end::configValue[]
 50    // end::contactEmail[]
 51
 52    // tag::configSystemBean[]
 53    // tag::inject[]
 54    @Inject
 55    // end::inject[]
 56    // tag::ConfigProperties[]
 57    @ConfigProperties
 58    // end::ConfigProperties[]
 59    // tag::systemConfig[]
 60    private ConfigSystemBean systemConfig;
 61    // end::systemConfig[]
 62    // end::configSystemBean[]
 63
 64    @GET
 65    @Produces(MediaType.APPLICATION_JSON)
 66    public Map<String, Properties> getAllConfig() {
 67        Map<String, Properties> configMap = new HashMap<String, Properties>();
 68        configMap.put("ConfigSources", getConfigSources());
 69        configMap.put("ConfigProperties", getConfigProperties());
 70        return configMap;
 71    }
 72
 73    @GET
 74    @Path("/contact")
 75    @Produces(MediaType.APPLICATION_JSON)
 76    // tag::getContactConfig[]
 77    public Properties getContactConfig() {
 78        Properties configProps = new Properties();
 79        // tag::getSourceName[]
 80        String sourceName = contactConfigValue.getSourceName();
 81        // end::getSourceName[]
 82        // tag::getSourceOrdinal[]
 83        int sourceOrdinal = contactConfigValue.getSourceOrdinal();
 84        // end::getSourceOrdinal[]
 85        // tag::getValue[]
 86        String value = contactConfigValue.getValue();
 87        // end::getValue[]
 88        configProps.put("SourceName", sourceName);
 89        configProps.put("SourceOrdinal", sourceOrdinal);
 90        configProps.put("Value", value);
 91        return configProps;
 92    }
 93    // end::getContactConfig[]
 94
 95    // tag::getSystemConfig[]
 96    @GET
 97    @Path("/system")
 98    @Produces(MediaType.APPLICATION_JSON)
 99    public Properties getSystemConfig() {
100        Properties configProps = new Properties();
101        // tag::systemProperties[]
102        configProps.put("system.httpPort", systemConfig.httpPort);
103        configProps.put("system.user", systemConfig.user);
104        configProps.put("system.password", systemConfig.password);
105        configProps.put("system.userPassword", systemConfig.userPassword);
106        configProps.put("system.contextRoot", systemConfig.contextRoot);
107        configProps.put("system.properties", systemConfig.properties);
108        // end::systemProperties[]
109        return configProps;
110    }
111    // end::getSystemConfig[]
112
113    //tag::getConfigSourcesClassMethod[]
114    public Properties getConfigSources() {
115        Properties configSource = new Properties();
116        // tag::getConfigSources[]
117        for (ConfigSource source : config.getConfigSources()) {
118        // end::getConfigSources[]
119            configSource.put(source.getName(), source.getOrdinal());
120        }
121        return configSource;
122    }
123    //end::getConfigSourcesClassMethod[]
124
125    //tag::getConfigProperties[]
126    public Properties getConfigProperties() {
127        Properties configProperties = new Properties();
128        // tag::getPropertyNames[]
129        for (String name : config.getPropertyNames()) {
130        // end::getPropertyNames[]
131            if (name.startsWith("system.")
132                || name.startsWith("query.")
133                || name.equals("role")) {
134                configProperties.put(name, config.getValue(name, String.class));
135            }
136        }
137        return configProperties;
138    }
139    //end::getConfigProperties[]
140}

Inject the ConfigSystemBean CDI bean by using the @Inject and @ConfigProperties annotations to create the systemConfig field variable, and add the getSystemConfig() class method.

The getSystemConfig() method retrieves the values of the system. configuration properties by using the systemConfig bean without having to inject them one by one and returns a Properties object that contains the values of the system. configuration properties.

Check out the endpoint that you created at the http://localhost:9085/query/config/system URL. You see all the configuration properties that are prefixed with system..

Testing the application

You will implement several endpoint tests to test the basic functionality of the query microservice. If a test failure occurs, then you might have introduced a bug into the code.

Create the QueryEndpointIT class.
query/src/test/java/it/io/openliberty/guides/query/QueryEndpointIT.java

QueryEndpointIT.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2023 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 2.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-2.0/
  8 *
  9 * SPDX-License-Identifier: EPL-2.0
 10 *******************************************************************************/
 11// end::copyright[]
 12package it.io.openliberty.guides.query;
 13
 14import static org.junit.jupiter.api.Assertions.assertEquals;
 15import static org.junit.jupiter.api.Assertions.assertNotNull;
 16import static org.junit.jupiter.api.Assertions.assertTrue;
 17
 18import org.junit.jupiter.api.AfterEach;
 19import org.junit.jupiter.api.BeforeEach;
 20import org.junit.jupiter.api.Test;
 21
 22import jakarta.json.JsonObject;
 23import jakarta.ws.rs.client.Client;
 24import jakarta.ws.rs.client.ClientBuilder;
 25import jakarta.ws.rs.core.Response;
 26
 27public class QueryEndpointIT {
 28
 29    private static String port = System.getProperty("http.port");
 30    private static String baseUrl = "http://localhost:" + port + "/query";
 31    private static String systemHost = System.getProperty("system.host");
 32
 33    private static Client client;
 34
 35    @BeforeEach
 36    public void setup() {
 37      client = ClientBuilder.newClient();
 38    }
 39
 40    @AfterEach
 41    public void teardown() {
 42        client.close();
 43    }
 44
 45    // tag::testQuerySystem[]
 46    @Test
 47    public void testQuerySystem() {
 48
 49        Response response = this.getResponse(baseUrl + "/systems/" + systemHost);
 50        this.assertResponse(baseUrl, response);
 51
 52        JsonObject jsonObj = response.readEntity(JsonObject.class);
 53        assertNotNull(jsonObj.getString("os.name"), "os.name is null");
 54        assertNotNull(jsonObj.getString("os.arch"), "os.arch is null");
 55        assertNotNull(jsonObj.getString("java.version"), "java.version is null");
 56        assertNotNull(jsonObj.getString("java.vendor"), "java.vendor is null");
 57
 58        response.close();
 59    }
 60    // end::testQuerySystem[]
 61
 62    // tag::testQueryConfigContact[]
 63    @Test
 64    public void testQueryConfigContact() {
 65
 66        Response response = this.getResponse(baseUrl + "/config/contact");
 67        this.assertResponse(baseUrl, response);
 68
 69        JsonObject json = response.readEntity(JsonObject.class);
 70        String value = json.getString("Value");
 71        assertTrue(value.contains("ol.guides.com"), "Value is wrong.");
 72        int ordinal = json.getInt("SourceOrdinal");
 73        String source = json.getString("SourceName");
 74        assertEquals(100, ordinal, "SourceOrdinal is not 100.");
 75        assertTrue(source.contains("microprofile-config.properties"),
 76               "SourceName is not right.");
 77        response.close();
 78    }
 79    // end::testQueryConfigContact[]
 80
 81    // tag::testQueryConfig[]
 82    @Test
 83    public void testQueryConfig() {
 84
 85        Response response = this.getResponse(baseUrl + "/config");
 86        this.assertResponse(baseUrl, response);
 87
 88        JsonObject json = response.readEntity(JsonObject.class);
 89        JsonObject props = json.getJsonObject("ConfigProperties");
 90        assertNotNull(props, "ConfigProperties is null.");
 91        String contactEmail = props.getString("query.contactEmail");
 92        assertTrue(contactEmail.contains("ol.guides.com"), "contactEmail is wrong.");
 93        JsonObject sources = json.getJsonObject("ConfigSources");
 94        assertNotNull(sources, "ConfigSources is null.");
 95        assertEquals(300, sources.getInt("EnvConfigSource"),
 96            "EnvConfigSource is not 300.");
 97        assertEquals(400, sources.getInt("SysPropConfigSource"),
 98            "SysPropConfigSource is not 400.");
 99        assertEquals(500, sources.getInt("server.xml.variables.config.source"),
100             "server.xml.variables.config.source is not 500.");
101        response.close();
102    }
103    // end::testQueryConfig[]
104
105    // tag::testQueryConfigSystem[]
106    @Test
107    public void testQueryConfigSystem() {
108
109        Response response = this.getResponse(baseUrl + "/config/system");
110        this.assertResponse(baseUrl, response);
111
112        JsonObject json = response.readEntity(JsonObject.class);
113        assertTrue(json.getInt("system.httpPort") > 9000, "system.httpPort is wrong.");
114        assertNotNull(json.getString("system.user"), "system.user is null.");
115        assertNotNull(json.getString("system.user"), "system.user is null.");
116        assertNotNull(json.getString("system.password"), "system.password is null.");
117        assertNotNull(json.getString("system.userPassword"),
118            "system.userPassword is null.");
119        response.close();
120    }
121    // end::testQueryConfigSystem[]
122
123    // tag::testUnknownHost[]
124    @Test
125    public void testUnknownHost() {
126        Response response = this.getResponse(baseUrl + "/systems/unknown");
127        this.assertResponse(baseUrl, response);
128
129        JsonObject json = response.readEntity(JsonObject.class);
130        assertEquals("Failed to reach the client unknown.", json.getString("fail"),
131            "Fail message is wrong.");
132        response.close();
133    }
134    // end::testUnknownHost[]
135
136    private Response getResponse(String url) {
137        return client.target(url).request().get();
138    }
139
140    private void assertResponse(String url, Response response) {
141        assertEquals(200, response.getStatus(), "Incorrect response code from " + url);
142    }
143
144}

query/pom.xml

 1<?xml version="1.0" encoding="UTF-8" ?>
 2<project xmlns="http://maven.apache.org/POM/4.0.0"
 3    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 5    <modelVersion>4.0.0</modelVersion>
 6
 7    <groupId>io.openliberty.guides</groupId>
 8    <artifactId>guide-microprofile-config-profile-query</artifactId>
 9    <version>1.0-SNAPSHOT</version>
10    <packaging>war</packaging>
11
12    <properties>
13        <maven.compiler.source>11</maven.compiler.source>
14        <maven.compiler.target>11</maven.compiler.target>
15        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16        <liberty.var.default.http.port>9085</liberty.var.default.http.port>
17        <liberty.var.default.https.port>9448</liberty.var.default.https.port>
18    </properties>
19
20    <dependencies>
21        <dependency>
22            <groupId>jakarta.platform</groupId>
23            <artifactId>jakarta.jakartaee-api</artifactId>
24            <version>10.0.0</version>
25            <scope>provided</scope>
26        </dependency>
27        <!-- tag::mpconfig[] -->
28        <dependency>
29            <groupId>org.eclipse.microprofile</groupId>
30            <artifactId>microprofile</artifactId>
31            <version>6.0</version>
32            <type>pom</type>
33            <scope>provided</scope>
34        </dependency>
35        <!-- end::mpconfig[] -->
36
37        <!-- For tests -->
38        <dependency>
39            <groupId>org.junit.jupiter</groupId>
40            <artifactId>junit-jupiter</artifactId>
41            <version>5.9.2</version>
42            <scope>test</scope>
43        </dependency>
44        <dependency>
45            <groupId>org.jboss.resteasy</groupId>
46            <artifactId>resteasy-client</artifactId>
47            <version>6.2.3.Final</version>
48            <scope>test</scope>
49        </dependency>
50        <dependency>
51            <groupId>org.jboss.resteasy</groupId>
52            <artifactId>resteasy-json-binding-provider</artifactId>
53            <version>6.2.3.Final</version>
54            <scope>test</scope>
55        </dependency>
56    </dependencies>
57
58    <build>
59        <finalName>${project.artifactId}</finalName>
60        <plugins>
61            <plugin>
62                <groupId>org.apache.maven.plugins</groupId>
63                <artifactId>maven-war-plugin</artifactId>
64                <version>3.3.2</version>
65            </plugin>
66            <plugin>
67                <groupId>io.openliberty.tools</groupId>
68                <artifactId>liberty-maven-plugin</artifactId>
69                <version>3.8.2</version>
70            </plugin>
71            <plugin>
72                <groupId>org.apache.maven.plugins</groupId>
73                <artifactId>maven-failsafe-plugin</artifactId>
74                <version>3.0.0</version>
75                <configuration>
76                    <!-- tag::systemPropertyVariables[] -->
77                    <systemPropertyVariables>
78                        <http.port>${liberty.var.default.http.port}</http.port>
79                        <system.host>localhost</system.host>
80                    </systemPropertyVariables>
81                    <!-- end::systemPropertyVariables[] -->
82                </configuration>
83            </plugin>
84        </plugins>
85    </build>
86</project>

See the following descriptions of the test cases:

  • testQuerySystem() verifies the /query/systems/{hostname} endpoint.

  • testQueryConfigContact() verifies the /query/config/contact endpoint.

  • testQueryConfig() verifies the /query/config endpoint.

  • testQueryConfigSystem() verifies the /query/config/system endpoint.

  • testUnknownHost() verifies that an unknown host or a host that does not expose their JVM system properties is correctly handled with a fail message.

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 the query service. If the tests pass, you see a similar output to the following example:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.query.QueryEndpointIT
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.703 s - in it.io.openliberty.guides.query.QueryEndpointIT

Results:

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

When you are done checking out the application, exit dev mode by pressing CTRL+C in the command-line sessions where you ran the system and query services.

Great work! You’re done!

You just learned how to use Microfile Config APIs to optimize configuration management for your microservices.

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

Guide Attribution

Optimizing configuration for microservices by Open Liberty is licensed under CC BY-ND 4.0

Copy file contents
Copied to clipboard

Prerequisites:

Great work! You're done!

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

Guide license