git clone https://github.com/openliberty/guide-microprofile-config-apis.git
cd guide-microprofile-config-apis
Optimizing configuration for microservices
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:
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:
-
The http://localhost:9085/query/systems/localhost URL returns the current OS and Java properties in JSON format. The URL retrieves the system property information for the
localhosthostname by making a request to thesystemservice at thehttp://localhost:<system.httpPort>/<system.contextRoot>/property/{property}URL. -
The http://localhost:9085/query/config/contact URL returns the source name, source ordinal, and value of the
query.contactEmailproperty. -
The http://localhost:9085/query/config URL returns all registered configuration sources and properties of the running
queryservice. -
The http://localhost:9085/query/config/system URL returns all the configuration properties that are prefixed with
system.
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 themicroprofile-config.propertiesfile.query/src/main/resources/META-INF/microprofile-config.properties
query/microprofile-config.properties
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 theSystemResourceclass.query/src/main/java/io/openliberty/guides/query/SystemResource.java
query/SystemResource.java
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 themicroprofile-config.propertiesfile.query/src/main/resources/META-INF/microprofile-config.properties
query/microprofile-config.properties
Define the system.properties property as a comma-separated list of system properties.
To use this configuration property,
Replace theSystemResourceclass.query/src/main/java/io/openliberty/guides/query/SystemResource.java
query/SystemResource.java
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 themicroprofile-config.propertiesfile.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 theConfigResourceclass.query/src/main/java/io/openliberty/guides/query/ConfigResource.java
query/ConfigResource.java
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:
-
The http://localhost:9085/query/config/contact URL returns the source name, source ordinal, and value of the
query.contactEmailproperty. -
The http://localhost:9085/query/config URL returns all registered configuration sources and properties of the running
queryservice.
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 theConfigSystemBeanclass.query/src/main/java/io/openliberty/guides/query/ConfigSystemBean.java
query/ConfigSystemBean.java
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 theConfigResourceclass.query/src/main/java/io/openliberty/guides/query/ConfigResource.java
query/ConfigResource.java
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 theQueryEndpointITclass.query/src/test/java/it/io/openliberty/guides/query/QueryEndpointIT.java
QueryEndpointIT.java
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/contactendpoint. -
testQueryConfig()verifies the/query/configendpoint. -
testQueryConfigSystem()verifies the/query/config/systemendpoint. -
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
Prerequisites:
Great work! You're done!
What did you think of this guide?
Thank you for your feedback!
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