-
Notifications
You must be signed in to change notification settings - Fork 71
Configuration
This article presents motivation for using configuration management frameworks when developing microservices for cloud-native architectures, introduces configuration management in KumuluzEE and presents basic concepts and steps needed to enrich KumuluzEE applications with provided configuration managements concepts.
Effective configuration management is important especially when designing microservices for cloud-native architectures. Ideally, developer is provided with options to inject configuration properties directly into the application code, with values dynamically loaded from a pool of provided configuration sources, such as environment variables, property files, internal storage, etc. Main advantages of this design pattern are:
- separation of configuration properties and application code,
- support for pluggable configuration sources with unified interface,
- monitoring changes in configuration sources and dynamic reconfiguration without the need for redeploying applications.
KumuluzEE microservices framework provides basic support for injecting configuration properties into microservices. It includes annotations for injecting configuration properties, which are dynamically loaded from basic configuration sources. Environment variables, YAML configuration files and java properties files are supported. Extensions for other external dedicated configuration sources such as etcd and Consul use the same interfaces and can be included as a KumuluzEE Config extension.
Annotations, interfaces and implementation for basic configuration sources are part of cdi KumuluzEE module.
Basic concepts for managing configuration with KumuluzEE are configuration parameter keys, configuration sources, ConfigurationUtil class and properly annotated properties bean. When developing microservices with KumuluzEE we first have to structure our configuration properties keys and define configuration sources with configuration values, then we can access defined configuration with ConfigurationUtil class or create a properties bean and extend it with appropriate configuration annotations. All those steps are explained in more detail in the following sections.
Configuration parameters are stored in the key-value format (e.g. port=8080). For better transparency, configuration keys are organised into a tree structure. Example configuration structure:
- kumuluzee
- name
- env
- name
- version
- server
- base-url
- http
- port
- datasources
- jndi-name
Configuration parameter key names reflect the tree structure of the configuration. In key names, individual layers of the tree structure are separated with dots. Example key from the configuration above is kumuluzee.env.name. Key names are written in spinal-case format, e.g. kumuluzee.server.base-url.
Configuration values can be stored in form of arrays. A configuration parameter key that represents an array ends with an array item index enclosed in square bracket, e.g. kumuluzee.array[1].key.
Configuration properties can be stored in different configuration sources. Value can be a primitive, an object or an array. Configuration is loaded from all available sources according to the priorities that are set with an ordinal number. If a key is not defined in a source with the highest priority, it is looked-up in the source with lower priority (and so on). All supported source formats are described further in this section. Sources are listed in order of priority, where the first source has the highest priority.
Configuration properties defined as system properties have the highest priority with an ordinal number 400. Configuration keys have to be written in standard format, e.g. kumuluzee.server.base-url.
Configuration properties defined as environment variables have the second highest priority with an ordinal number 300. Configuration keys have to be written in standard format for environment variables, i.e. with capital case letters and tree levels separated with underscores. Hyphens and square brackets in key names can be omitted. Configuration framework will first look for a key without hyphens, e.g.:
KUMULUZEE_SERVER_BASEURL=host1.services.local
RESTCONFIG_STRINGPROPERTY0=Monday
If a key in this format does not exist, it will then try to look for a key with hyphens and square brackets, e.g.:
KUMULUZEE_SERVER_BASE-URL=host1.services.local
REST-CONFIG_STRING-PROPERTY[0]=Monday
Framework supports configuration files in YAML and Java properties formats. Configuration files have an ordinal number 100. Framework loads one configuration file that is present in the classpath, trying to load them in the following order:
- config.yml
- config.yaml
- config.properties
- META-INF/microprofile-config.properties
File name can be overwritten with a system property com.kumuluz.ee.configuration.file.
Configuration in YAML format is loaded from file config.yml or config.yaml (file name can be overwritten with a system property com.kumuluz.ee.configuration.file) stored in resources folder. File structure mirrors the tree structure of configuration keys. Example file:
kumuluzee:
name: customer-service
env:
name: dev
version: 1.0.0
server:
base-url: http://localhost:8081
http:
port: 8081
datasources:
- jndi-name: jdbc/SampleDS
If a YAML file is not present, configuration is loaded from config.properties file (file name can be overwritten with a system property com.kumuluz.ee.configuration.file) stored in resources folder. Example file:
kumuluzee.name=customer-service
kumuluzee.env.name=dev
kumuluzee.version=1.0.0
kumuluzee.server.base-url=http://localhost:8081
kumuluzee.server.http.port=8081
kumuluzee.datasources[0].jndi-name=jdbc/SampleDS
Configuration properties defined in Java properties file have the lowest priority.
There are two ways of accessing configuration: manually, using methods from ConfigurationUtil class, or by using a properties bean that automatically loads configuration. Both methods are described in this section.
ConfigurationUtil class can be used for retrieving values of configuration parameters from the configuration
framework. It is implemented as a singleton that is retrieved with the getInstance()
method. Configuration values can
be retrieved with the following methods, depending on their type:
- Optional get(String key)
- Optional getBoolean(String key)
- Optional getInteger(String key)
- Optional getLong(String key)
- Optional getDouble(String key)
- Optional getFloat(String key)
Example call:
Integer port = ConfigurationUtil.getInstance().getInteger("kumuluzee.server.http.port");
A CDI bean can be used for retrieving and storing configuration values during application lifecycle. Configuration can be injected into any application scoped bean. A pattern with one dedicated properties bean for storing and accessing configuration is encouraged. Configuration properties can be stored in fields of primitive types (String, Boolean, Integer, Long, Float and Double), or in a custom nested classes. All fields shall have publicly available getter and setter methods.
An example properties bean that can be used to access configuration properties:
@ApplicationScoped
public class ConfigPropertiesExample {
private String name;
private String env;
private Server server;
// getter and setter methods
}
To enable automatic configuration injection, we have to annotate our properties bean with @ConfigBundle
annotation.
Annotation @ConfigBundle
takes one parameter that represents the first part of the key, typically a prefix that is
the same for all keys defined in the bean. If value is not provided, it is set to bean name in spinal-case format (e.g.
config-properties-example). To access configuration values at root level, set the value to "."
.
Second part of the key is taken from the field name. Field name in example bellow maps to key kumuluzee.name.
This behavior can be overwritten with annotation @ConfigValue
by specifying second part of the key with parameter
value, which overrides field name used to form a configuration key (e.g. kumuluzee.env.name).
At bean initialisation, all fields with publicly available setters will be initialised from configuration sources according to specified key names.
An example properties bean that with configuration annotations:
@ApplicationScoped
@ConfigBundle("kumuluzee")
public class ConfigPropertiesExample {
private String name;
@ConfigValue("env.name")
private String env;
private Server server;
// getter and setter methods
}
public class Server {
private String baseUrl;
@ConfigValue("http.port")
private Integer port;
// getter and setter methods
}
This bean can then be injected and used anywhere in application to access injected configuration properties. Example:
@Inject
private ConfigPropertiesExample configProperties;
String url = configProperties.getServer().getBaseUrl();
KumuluzEE version >=3.3.0 has a built in feature called Configuration Decoder that can optionally be implemented to
intercept and apply transformation to a value that is being read from the configuration. To use the feature simply implement the ConfigurationDecoder
interface and register it using a service file. For example:
public class CustomConfigurationDecoder implements ConfigurationDecoder {
@Override
public boolean shouldDecode(String key) {
return "rest-config.encoded-property".equals(key);
}
@Override
public String decode(String key, String value) {
return new String(DatatypeConverter.parseBase64Binary(value));
}
}
Remember to register the implementation in a service file named com.kumuluz.ee.configuration.ConfigurationDecoder
.
An example microservice utilizing the Configuration Decoder can be found here.
In this article we presented an importance of configuration management and showed basic steps for implementing efficient configuration management with KumuluzEE framework. Additional configuration sources such as etcd and Consul with corresponding documentation and tutorials can be found in extension KumuluzEE Config. There are also a few samples, basic configuration sample and samples for etcd and Consul extensions.