When we are developing an application, it is usually a good practice to externalize some aspects of the application so we can change its runtime behavior without the need to change the code. This is what we call Configuration. A few examples may include the application language, the currency symbol, the username and password to connect to the database or a system path to store uploaded files.
In a Monolithic Architecture, you may get away from Configuration. It is not a big deal to perform a couple more builds to change some parameters. In the Microservices world, it is impossible.
A Microservices deployment will often run hundreds of microservice instances and use third-party services like a registry or an API Gateway. The connection information among microservices and third-party services are usually unknown at development time. Only at runtime are we able to apply the correct information to set up our application – this is especially true when using the Cloud services. To address the need to dynamically apply and modify configuration settings in a microservices deployment, a standard mechanism is needed.
It is no coincidence that the first specification defined for the MicroProfile initiative was precisely about Configuration. So, what do we get with MicroProfile Config?
Familiarize yourself with the API
The MicroProfile Configuration API is fairly simple and straightforward. To get you started you only need to know a handful of classes.
The annotation also allows you to define a name and a
defaultValue. The name is the key to the configuration property to look up the configuration value. The
defaultValue allows you to set predefined value if the configuration property cannot be found.
@Inject @ConfigProperty(name = "application.currency") private String currency; @Inject @ConfigProperty(name = "application.list.maxSize", defaultValue="10") private Integer maxSize;
Notice that you are using a different type for each of the configuration annotations. It is smart enough to check the type being injected and do the conversion for you. This works for the majority of common Java types, like
Boolean, and even Java 8
Optional. It also works for native types. The default converters should cover most of the use cases. For the rare occasion that they are not enough, you can also implement your own Converter – a topic that will be covered in a future post.
What if I don’t have CDI available?
Not a problem. You can statically call the method
org.eclipse.microprofile.config.ConfigProvider.getConfig() which will return you an instance of
org.eclipse.microprofile.config.Config. From this
Config object, you can call
getOptionalValue to retrieve configuration values.
final Config config = ConfigProvider.getConfig(); config.getValue("application.currenty", String.class); config.getOptionalValue("application.list.maxSize”, Integer.class);
The Config instance could also be injected into a CDI aware bean with @Inject.
@Inject final Config config;
We now know how to retrieve configuration values, but where are they coming from? The answer is simple. A >
ConfigSource is exactly what its name says: a source for configured values. The Config API uses all configured implementations of the type org.eclipse.microprofile.config.spi.ConfigSource to look up the property in question.
ConfigSource types are already set up for you by default.
This is a key value pair file that you can place in your application WAR file (or JAR file) to load configuration values.
ConfigSource System Properties
This source will read any key-value pair passed down to the JVM startup prefixed with
-D to load configuration values.
ConfigSource Environment Variables
This source will load the configuration values from the OS environment values.
What if you have the same configuration key in multiple sources?
ConfigSource types have a priority. The priority will be used to order the
ConfigSource implementations to search for the configuration key. It starts with the highest one in priority and it falls back to the next
ConfigSource if no key is found. Once it is found, the search ends and the config value from that
ConfigSource is used. The value in the
ConfigSource with the highest ordinal takes precedence.
By default, the priority order of the supplied ConfigSource types is Environment Variables, System Properties and finally
microprofile-config.properties. This allows you to override configuration in runtime. You can provide a list of all your configurations in
microprofile-config.properties and then override a value using an Environment Variable.
You can find a few samples around MicroProfile Config in TomEE. Please check the following Github project and try it out:
Instructions to run the sample can be found in the project README file.
For additional information, please check:
MicroProfile Config is a fairly small and simple specification, and at the same time, it is extremely powerful. Use it to customize your application and externalize configuration that you can change to adapt the runtime behavior for your needs.
In the future, we will dive deeper into MicroProfile Config. We will show you how to create your own ConfigSource to extract configuration values from a Database table and how to add Converters to convert values to nonstandard types. Stay tuned.
If you like to be involved or just follow the discussion around MicroProfile Config, just signup into the MicroProfile mailing list. It’s public and free!