Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/openliberty/guide-microprofile-config

A guide on how to provide external configuration to microservices using MicroProfile Config: https://openliberty.io/guides/microprofile-config.html
https://github.com/openliberty/guide-microprofile-config

Last synced: 2 months ago
JSON representation

A guide on how to provide external configuration to microservices using MicroProfile Config: https://openliberty.io/guides/microprofile-config.html

Awesome Lists containing this project

README

        

// Copyright (c) 2017, 2023 IBM Corporation and others.
// Licensed under Creative Commons Attribution-NoDerivatives
// 4.0 International (CC BY-ND 4.0)
// https://creativecommons.org/licenses/by-nd/4.0/
//
// Contributors:
// IBM Corporation
:projectid: microprofile-config
:page-layout: guide-multipane
:page-duration: 20 minutes
:page-releasedate: 2018-03-09
:page-guide-category: microprofile
:page-essential: false
:page-description: Learn how to use the MicroProfile Config specification to externalize configuration data for an application.
:page-tags: ['MicroProfile']
:page-permalink: /guides/{projectid}
:page-related-guides: ['rest-intro', 'cdi-intro', 'microprofile-config-intro']
:common-includes: https://raw.githubusercontent.com/OpenLiberty/guides-common/prod
:source-highlighter: prettify
:page-seo-title: Externalizing configuration for Java microservices using Eclipse MicroProfile Config
:page-seo-description: A tutorial and example on how to externalize and inject static and dynamic configuration properties for Java microservices through configuration sources using Eclipse MicroProfile Config.
:guide-author: Open Liberty
= Configuring microservices

[.hidden]
NOTE: This repository contains the guide documentation source. To view the guide in published form, view it on the https://openliberty.io/guides/{projectid}.html[Open Liberty website].

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

// =================================================================================================
// What you'll learn
// =================================================================================================

== 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
// =================================================================================================

[role=command]
include::{common-includes}/gitclone.adoc[]

[role=command]
include::{common-includes}/twyb-intro.adoc[]

// static guide instructions:
ifndef::cloud-hosted[]
You can access the following two microservices to test their availability:

* http://localhost:9080/system/properties[http://localhost:9080/system/properties^] retrieves the information for a specific host

* http://localhost:9080/inventory/systems[http://localhost:9080/inventory/systems^] retrieves the information for a list of all previously registered hosts

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:

* http://localhost:9080/config[http://localhost:9080/config^]
endif::[]

// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
Open another command-line session by selecting **Terminal** > **New Terminal** from the menu of the IDE. Run the following curl command to test the availability of the ***system*** microservice and retrieve the system information:
```bash
curl -s http://localhost:9080/system/properties | jq
```

Run the following curl command to test the availability of the **inventory** microservice and retrieve the information for a list of all previously registered hosts:
```bash
curl -s http://localhost:9080/inventory/systems | jq
```

In addition, you can run the following curl command to access a third microservice, which retrieves and aggregates all of the configuration properties and sources that are added throughout this guide.
```bash
curl -s http://localhost:9080/config | jq
```
endif::[]

[role=command]
include::{common-includes}/twyb-end.adoc[]

// =================================================================================================
// Ordering multiple configuration sources
// =================================================================================================
== Ordering multiple configuration sources

// static guide instructions:
ifndef::cloud-hosted[]
Now, navigate to the `start` directory to begin.
endif::[]

// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
To begin, run the following command to navigate to the **start** directory:
```bash
cd /home/project/guide-microprofile-config/start
```
endif::[]

[role=command]
include::{common-includes}/devmode-lmp33-start.adoc[]

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 `` 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 [hotspot=ordinal]`META-INF/microprofile-config.properties` configuration property file on the classpath has a default ordinal of 100.

Access the [hotspot]`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
[source, properties, linenums, role='code_column']
----
include::finish/src/main/resources/META-INF/microprofile-config.properties[]
----

// =================================================================================================
// Injecting static configuration
// =================================================================================================
== Injecting static configuration

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

pom.xml
[source,xml,linenums,role="code_column"]
----
include::finish/pom.xml[]]
----

server.xml
[source,xml,linenums,role="code_column"]
----
include::finish/src/main/liberty/config/server.xml[]
----

Now navigate to the [hotspot file=2]`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 [hotspot=inventory-port-number file=2]`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
[source, properties, linenums, role='code_column']
----
include::finish/src/main/resources/META-INF/microprofile-config.properties[]
----

To use this configuration property,
[role="code_command hotspot file=3", subs="quotes"]
----
#Create the `InventoryConfig.java` class.#
`src/main/java/io/openliberty/guides/inventory/InventoryConfig.java`
----
InventoryConfig.java
[source, Java, linenums, role='code_column hide_tags=copyright,inject-inMaintenance,custom-converter,isInMaintenance,getEmail']
----
include::finish/src/main/java/io/openliberty/guides/inventory/InventoryConfig.java[]
----

Inject the [hotspot=guides-port-number file=3]`io_openliberty_guides_port_number` property, and add the [hotspot=getPortNumber file=3]`getPortNumber()` class method to the [hotspot file=3]`InventoryConfig.java` file.

The [hotspot=inject-port-number file=3]`@Inject` annotation injects the port number directly, the injection value is static and fixed on application starting.

The [hotspot=getPortNumber file=3]`getPortNumber()` method directly returns the value of `portNumber` because it has been injected.

// =================================================================================================
// Injecting dynamic configuration
// =================================================================================================
== 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 Liberty 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
// =================================================================================================
=== Creating custom configuration sources
CustomConfigSource.json
[source, Json, linenums, role='code_column']
----
include::finish/resources/CustomConfigSource.json[]
----

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

A [hotspot file=0]`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 [hotspot=2 file=0]`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,

[role="code_command hotspot file=1", subs="quotes"]
----
#Create the `CustomConfigSource` class.#
`src/main/java/io/openliberty/guides/config/CustomConfigSource.java`
----
CustomConfigSource.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/config/CustomConfigSource.java[]
----

The [hotspot=getProperties file=1]`getProperties()` method reads the key value pairs from the [hotspot file=0]`resources/CustomConfigSource.json` JSON file and writes the information into a map.

Finally, register the custom configuration source.

[role="code_command hotspot file=2", subs="quotes"]
----
#Create the configuration file.#
`src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource`
----

org.eclipse.microprofile.config.spi.ConfigSource
[source, Java, linenums, role='code_column']
----
include::finish/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource[]
----

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

// =================================================================================================
// Enabling dynamic configuration injection
// =================================================================================================
=== 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,

[role="code_command hotspot", subs="quotes"]
----
#Replace the `InventoryConfig.java` class.#
`src/main/java/io/openliberty/guides/inventory/InventoryConfig.java`
----
InventoryConfig.java
[source, Java, linenums, role='code_column hide_tags=copyright,custom-converter,getEmail']
----
include::finish/src/main/java/io/openliberty/guides/inventory/InventoryConfig.java[]
----
Inject the [hotspot=inject-inMaintenance file=0]`io_openliberty_guides_inventory_inMaintenance` property, and add the [hotspot=isInMaintenance file=0]`isInMaintenance()` class method.

The [hotspot=inject file=0]`@Inject` and [hotspot=configPropety file=0]`@ConfigProperty` annotations inject the [hotspot=4 file=1]`io_openliberty_guides_inventory_inMaintenance` configuration property from the [hotspot file=1]`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 [hotspot=inMaintenanceGet file=0]`inMaintenance.get()` method, the `Provider<>` interface picks up the latest value of the [hotspot=4 file=1]`io_openliberty_guides_inventory_inMaintenance` property from configuration sources.

CustomConfigSource.json
[source, Json, linenums, role='code_column']
----
include::finish/resources/CustomConfigSource.json[]
----

// =================================================================================================
// Creating custom converters
// =================================================================================================
== 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`.

To convert configured Strings to an arbitrary class type, such as the `Email` class type,
[role="code_command hotspot", subs="quotes"]
----
#Replace the `Email` Class.#
`src/main/java/io/openliberty/guides/config/Email.java`
----
Email.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/config/Email.java[]
----

To use this [hotspot=email file=0]`Email` class type, add a custom converter by implementing the generic interface `org.eclipse.microprofile.config.spi.Converter`. The Type parameter of the interface is the target type the String is converted to.

[role="code_command hotspot file=1", subs="quotes"]
----
#Create the `CustomEmailConverter` class.#
`src/main/java/io/openliberty/guides/config/CustomEmailConverter.java`
----
CustomEmailConverter.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/config/CustomEmailConverter.java[]
----

This implements the `Converter` interface.

To register your implementation,
[role="code_command hotspot file=2", subs="quotes"]
----
#Create the configuration file.#
`src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter`
----
org.eclipse.microprofile.config.spi.Converter
[source, Java, linenums, role='code_column']
----
include::finish/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.Converter[]
----
Add the fully qualified class name of the custom converter into it.

To use the custom [hotspot=customConfig file=2]`Email` converter,
[role="code_command hotspot file=3", subs="quotes"]
----
#Replace the `InventoryConfig` class.#
`src/main/java/io/openliberty/guides/inventory/InventoryConfig.java`
----
InventoryConfig.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/InventoryConfig.java[]
----
Inject the [hotspot=custom-converter file=3]`io_openliberty_guides_email` property, and add the [hotspot=getEmail file=3]`getEmail()` method.

// =================================================================================================
// Adding configuration to the service
// =================================================================================================
== Adding configuration to the microservice

To use externalized configuration in the `inventory` service,
[role="code_command hotspot", subs="quotes"]
----
#Replace the `InventoryResource` class.#
`src/main/java/io/openliberty/guides/inventory/InventoryResource.java`
----
InventoryResource.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/main/java/io/openliberty/guides/inventory/InventoryResource.java[]
----
To add configuration to the `inventory` service, the [hotspot=config-injection file=0]`InventoryConfig` object is injected to the existing class.

The port number from the configuration is retrieved by the [hotspot=config-port file=0]`inventoryConfig.getPortNumber()` method and passed to the [hotspot=config-port file=0]`manager.get()` method as a parameter.

To determine whether the inventory service is in maintenance or not (according to the configuration value), [hotspot=isInMaintenance file=0]`inventoryConfig.isInMaintenance()` class method is used. If you set the [hotspot=4 file=1]`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 [hotspot=getEmail file=0]`inventoryConfig.getEmail()` method.

CustomConfigSource.json
[source, Json, linenums, role='code_column']
----
include::finish/resources/CustomConfigSource.json[]
----

// =================================================================================================
// Building and running the application
// =================================================================================================

[role=command]
include::{common-includes}/devmode-build.adoc[]

// static guide instructions:
ifndef::cloud-hosted[]
While the Liberty is running, the following two microservices should be available to access:

* http://localhost:9080/system/properties[http://localhost:9080/system/properties^]

* http://localhost:9080/inventory/systems[http://localhost:9080/inventory/systems^]

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

* http://localhost:9080/config[http://localhost:9080/config^]
endif::[]

// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
While the Liberty is running, run the following curl command to access the ***system*** microservice:
```bash
curl -s http://localhost:9080/system/properties | jq
```

and run the following curl command to access the ***inventory*** microservice:
```bash
curl -s http://localhost:9080/inventory/systems | jq
```

You can find the service that retrieves configuration information that is specific to this guide by running the following curl command:
```bash
curl -s http://localhost:9080/config | jq
```
endif::[]

The [hotspot=2 file=0]`config_ordinal` value of the custom configuration source is set to [hotspot=2 file=0]`150`. It overrides configuration values of the default [hotspot file=1]`microprofile-config.properties` source, which has a [hotspot=ordinal file=1]`config_ordinal` value of [hotspot=ordinal file=1]`100`.

CustomConfigSource.json
[source, Json, linenums, role='code_column']
----
include::finish/resources/CustomConfigSource.json[]
----

microprofile-config.properties
[source, properties, linenums, role='code_column']
----
include::finish/src/main/resources/META-INF/microprofile-config.properties[]
----

// static guide instructions:
ifndef::cloud-hosted[]
Play with this application by changing configuration values for each property in the [hotspot file=0]`resources/CustomConfigSource.json` file. Your changes are added dynamically, and you do not need to restart the Liberty. Refresh http://localhost:9080/config[http://localhost:9080/config^] to see the dynamic changes.

For example, change [hotspot=4 file=0]`io_openliberty_guides_inventory_inMaintenance` from `false` to `true`, then try to access http://localhost:9080/inventory/systems[http://localhost:9080/inventory/systems^] again. The following message displays: `ERROR: Service is currently in maintenance`.
endif::[]

// cloud-hosted guide instructions:
ifdef::cloud-hosted[]
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 Liberty. Rerun the following curl command to see the dynamic changes:
```bash
curl -s http://localhost:9080/config | jq
```

For example, change ***io_openliberty_guides_inventory_inMaintenance*** from ***false*** to ***true***, then try to access http://localhost:9080/inventory/systems again by running the following curl command:
```bash
curl -s http://localhost:9080/inventory/systems | jq
```

The following message displays: ***ERROR: Service is currently in maintenance***.
endif::[]

// =================================================================================================
// Testing the application
// =================================================================================================

== Testing the application

[role="code_command hotspot", subs="quotes"]
----
#Create the `ConfigurationIT` class.#
`src/test/java/it/io/openliberty/guides/config/ConfigurationIT.java`
----
ConfigurationIT.java
[source, Java, linenums, role='code_column hide_tags=copyright']
----
include::finish/src/test/java/it/io/openliberty/guides/config/ConfigurationIT.java[]
----

microprofile-config.properties
[source, properties, linenums, role='code_column']
----
include::finish/src/main/resources/META-INF/microprofile-config.properties[]
----

The [hotspot=testInitialServiceStatus file=0]`testInitialServiceStatus()` test case reads the value of the [hotspot=inventory-in-maintenance file=1]`io_openliberty_guides_inventory_inMaintenance` configuration property in the [hotspot file=1]`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 [hotspot=inventory-in-maintenance file=1]`io_openliberty_guides_inventory_inMaintenance` configuration property is set to `false` by default, the [hotspot=testPutServiceInMaintenance file=0]`testPutServiceInMaintenance()` test case first checks that the inventory service is not in maintenance in the beginning. Next, this test switches the value of the [hotspot=inventory-in-maintenance file=1]`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 [hotspot=testChangeEmail file=0]`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.

[role=command]
include::{common-includes}/devmode-test.adoc[]

You see the following output:

[source, role="no_copy"]
----
-------------------------------------------------------
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 [hotspot=setup file=0]`setup()` method of the [hotspot file=0]`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.

[role=command]
include::{common-includes}/devmode-quit-ctrlc.adoc[]

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

include::{common-includes}/attribution.adoc[subs="attributes"]