How to use Spring Cloud Config Server for Microprofile apps

One of my home projects uses microservices developed with Spring Boot as well as MicroProfile. This made me wonder if I could use the Spring Cloud Config Server to load the configuration for all these microservices. Using the Spring Cloud Config Server for Spring Boot applications is easy, but how do you read the configuration properties from the Spring Cloud Config Server in a MicroProfile microservice?

In a previous blog about the MicroProfile Specification, I wrote that the Config API has a mechanism to implement custom Config Sources to use next to the three default Config Sources:

  • system properties,
  • environment properties and the
  • microprofile-config.properties file.

So all we have to do is to implement a custom Config Source to read the values from the Config Server.

For the purpose of this blog I have created a sample Spring Cloud Config Server application, which contains the configuration value environment for two profiles, local and production. A second (MicroProfile) application, containing a REST service which returns the environment to the caller, reads the environment from the Spring Cloud Config Server using the @ConfigProperty-annotation which is loaded through this custom Config Source.

The code fragments in this blog are simplified. For a complete implementation see the sources in my Github repository.

Implementing the custom Config Source

To implement a custom Config Source, we need to implement the Config Source interface, which is shown below.

package nu.misano.microprofile.configclient.springconfig;
 
import ...
 
public class SpringCloudConfigSource implements ConfigSource {
 
   @Override
   public Map<String, String> getProperties() {
       return null;
   }
 
   @Override
   public Set<String> getPropertyNames() {
 
   }
 
   @Override
   public int getOrdinal() {
       return 0;
   }
 
   @Override
   public String getValue(String key) {
       return null;
   }
 
   @Override
   public String getName() {
       return null;
   }
}

This interface contains the following methods to implement

  • getName
    Returns the name of this Config Source, in this example SpringCloudConfigSource.
  • getOrdinal
    Returns the order in which the properties are loaded.

In this example I use 500. A higher the number means that the properties in this Config Source have a higher priority than the same properties in Config Sources with lower ordinals. The standard ordinal values for the default Config Sources are

      • 400 for the system properties,
      • 300 for the environment properties and
      • 100 for the microprofile-config properties.
  • getProperties
    Returns all the properties loaded in this Config Source.
  • getPropertyNames
    Returns all the names of the properties loaded in this Config Source.
  • getValue
    Returns the value of the key property.

The most important method in this interface is the getValue method. The default implementation of the getOrdinal and the getProperties method use the getValue method. With it we can implement a call to the Spring Cloud Config Server to get the value of a config property.

Using the REST Client

The easiest way to get the values is to use the REST Client API, but since the ConfigSource is not a CDI bean, we can not inject the REST Client as we would normally do with the  @RestClient annotation. Luckily we can use the RestClientBuilder to build our REST Client programmatically, which I wrote about in another blog.

As the REST call to the Config Server would be something like https://my.configserver.com/myApplication/myProfile we need to configure the following properties to call the Config Server to get the config properties from the Config Server.

  • url – the url of the Config Server
  • application – the name of the client application
  • profile – the current profile of the client application.

As we also cannot inject the config properties we need for the RestClient, we need another way to get these properties. For this we can use the ConfigProvider, which allows us to get the config properties programmatically instead of declarative injection. The method below builds a config consisting of the default Config Sources.

private Config getConfig() {
   return ConfigProviderResolver.instance().getBuilder().addDefaultSources().build();
}

Time to configure the three properties needed for the REST Client in either of the three default Config Sources. For this example I have added these properties to the microprofile-config properties file. But this can be overwritten using the system properties (or environment properties).

Now we can create the REST Client using the url property.

SpringCloudConfigServerApi restClient =  RestClientBuilder.newBuilder()
       .baseUrl(new URL(url))
       .build(SpringCloudConfigServerApi.class);

The SpringCloudConfigServerApi is an interface representing the call to our Spring Cloud Config Server. It contains a method getConfig with the application and profile parameters, which we can call to obtain the config properties for that application and profile.

SpringConfigResponse response = restClient.getConfig(application, profile);

The SpringConfigResponse is a class representing the JSON output of the Spring Cloud Config Server.

Implementing the methods

Now that we have a response, we can translate this to a map of properties and values and return the value needed for the getValue method. The SpringConfigResponse class contains a list of PropertySource objects, which each contain a map of properties and values. For this example we only need the one PropertySource returned.

We can use this to implement the getValue method and to implement the other Config Source methods getProperties and getProperyNames. We could also call the Spring Cloud Config Server, but for performance reasons it is better to cache the properties loaded and only call the Config Server when the properties are not loaded yet.

private Map<String, String> configProperties = null;
 
@Override
public Map<String, String> getProperties() {
   if (configProperties == null) {
       configProperties = loadProperties();
   }
   return configProperties;
}
 
@Override
public Set<String> getPropertyNames() {
   return getProperties() != null ? getProperties().keySet() : emptySet();
}
 
@Override
public String getValue(String key) {
   return getProperties() != null ? getProperties().get(key) : null;
}
 
@Override
private Map<String, String> loadProperties() {
Config config = getConfig();
String url = config.getValue("url", String.class);
String application = config.getValue("application", String.class);
String profile = config.getValue("profile", String.class);
 
SpringCloudConfigServerApi restClient = RestClientBuilder.newBuilder()
           .baseUrl(new URL(url))
           .connectTimeout(1, TimeUnit.SECONDS)
           .readTimeout(1, TimeUnit.SECONDS)
           .build(SpringCloudConfigServerApi.class);
SpringConfigResponse response = restClient.getConfig(application, profile);
return response.getPropertySources().stream()
       .findFirst()
       .map(PropertySource::getSource)
       .orElse(null);
}

Registering the custom Config Source

The application needs to know about the custom Config Source. Therefore, we need to register the custom Config Source to the Service Provider, using the Service Provider Interfaces (SPI) mechanism. By adding a file named org.eclipse.microprofile.config.spi.ConfigSource to the META-INF/services directory containing the fully qualified class name of the custom Config Source. In our case this would be:

nu.misano.microprofile.configclient.springconfig.SpringCloudConfigSource

Summary

This article showed how to get configuration properties for a MicroProfile-based application from a Spring Cloud Config server using a MicroProfile REST Client.

Some additional tips:

The REST Client uses the Config API as well, so we need to prevent that retrieving config properties ends up in an infinite loop (as every time the REST Client is called, our Config Source is also called). So I have added an isLoading boolean as a semaphore to the Config Source, which returns null in the getProperties method when true (as we are already busy loading the config properties).

We have loaded only one PropertySource in our example. If you need to load more PropertySources for your application, you could consider implementing a ConfigSourceProvider, which can load multiple Config Sources. More information is provided here.

Resources

MicroProfile Specification – https://microprofile.io
MicroProfile Config – https://microprofile.io/microprofile-config/
MicroProfile REST Client – https://microprofile.io/microprofile-rest-client/
Spring Cloud Config Server – https://docs.spring.io/spring-cloud-config/docs/current/reference/html/
Source code – https://github.com/Misano9699/microprofile-spring-config-server

Leave a Reply

Your email address will not be published. Required fields are marked *