Spring Boot Configuration Externalization with Azure App Configuration

Tausif Vhora
Globant
Published in
6 min readAug 3, 2023

Microservice architecture follows The Twelve-Factor App (12factor.net). It is recommended in the 12-Factor principle that application configurations should be detached from the executable application. Externalized configurations do not only provide environment-agnostic executables but also provide an easy and secure way to manage the configurations at runtime without having extended downtime.

Microsoft Azure provides a similar functionality via the App Configuration service, which can easily be integrated with the Spring Boot application. With this App Configuration service, developers can have all their application’s configurable parameters externalized to the Azure app.

The whole task of making the application’s configurable parameters externalized via Azure App service is divided into three sub-tasks, mainly as below, which will be covered in detail in subsequent sections.

  1. Actions Required on Azure App Configuration.
  2. Required Configurations in Microservice Application.
  3. Use Cases to Access Parameters in the Application Component.

1. Actions Required on Azure App Configuration

The first step is to create an “App Configuration” resource on the Azure web portal. After that, one can add all configurable parameters in that App-Configuration.

If we want to have on-off toggling for any functionality in the application, we can also create a feature flag to take advantage of it.

Please follow the below steps for the setup mentioned above.

Step 1: Create an Account in Azure DevOps. Create an App Configuration resource.

Screen to create new App Configuration

Step 2: Add new configurations in the app configuration resource.

Screen to create a new configuration (key & Json-value)

Step 3: Add a new feature flag in the app configuration resource.

Screen to create a new feature flag

Step 4: Go to the “Access Keys” section and copy the connection string value to be used in the Spring Boot configuration.

Screen to copy connection-string

Caveat: Above screens are as per the current UI of the Azure portal. (Please follow Azure resource for the latest change here: https://learn.microsoft.com/en-us/azure/azure-app-configuration/)

2. Required Configurations in Microservice Application

After setting up all configurable parameters in the Azure cloud, the second step is configuring the Spring Boot application to connect to the Azure app and fetching the configurations at server startup.

Please note that connection parameters and required key configurations must be added in bootstrap.yml.

Let's go through the steps below:

Step 1: Add the following dependencies in build.gradle.

dependencies {
implementation 'com.azure.spring:spring-cloud-azure-appconfiguration-config-web:5.2.0'
implementation 'com.azure.spring:spring-cloud-azure-feature-management-web:5.2.0'
implementation 'com.azure:azure-sdk-bom:1.2.13'
implementation 'com.azure:azure-data-appconfiguration:1.4.5'
}

Step 2: Add connection details and configuration details in bootstrap.yml.

  • connection-string: you would have copied from the portal as mentioned in the above steps.
  • key-filter: key filter is the key you assigned to your configurable parameter. (e.g., “screenConfigurations” is the key used in the above example) values with those keys will be fetched from the cloud.
  • label-filter: to segregate the same property for different environments.
spring:
cloud:
azure:
appconfiguration:
stores:
- connection-string: Endpoint=https://xyz.azconfig.io;Id=****;Secret=*************
selects:
- key-filter: screenConfigurations
label-filter: dev
- key-filter: filterKey
label-filter: dev
feature-flags:
enabled: true
label-filter: dev

Step 3: This is an optional configuration. It is only required when one wants to access the configurable parameters via @ConfigurationProperties annotation.

Annotate the pilot class of your Spring Boot application with @ConfigurationPropertiesScan and @EnableConfigurationProperties annotation.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@ConfigurationPropertiesScan
@EnableConfigurationProperties
@SpringBootApplication
public class AppConfig {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}

3. Use Cases to Access Parameters in the Application Component

After finishing above sections 1 and 2, your application will be ready to read configurable parameters via different constructs provided by Spring Boot like @Value or @ConfigurationProperties annotation.

Now, let's see the different use cases to access parameter values and feature flags in our application.

Use Case 1: Accessing configuration values via @Value annotation and @ConfigurationProperties class

Consider that you have different screens in your applications. Each screen has two properties called “enabled” and “sizeable”. And, you have configured its values as JSON in your Azure app configuration using the key “screenConfigurations” (mentioned above inbootstrap.yml) and its JSON value is as below:

{
"screens": [
{
"gymRegistration": {
"enabled": true,
"sizeable": false
}
},
{
"libraryRegistration": {
"enabled": true,
"sizeable": false
}
}
]
}

The application will fetch the above properties at the server startup and map with the class “ScreenProperties.java” which is annotated with @ConfigurationProperties as below:

import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import lombok.Data;

@ConfigurationProperties
@Data
public class ScreenProperties {
private Map<String, Map<String, Screen>> screens;
public static record Screen (boolean enabled, boolean sizeable){
}
}

Just in case, to access the individual property directly in the component, one can use @value annotation with key name as mentioned below code.

import java.net.MalformedURLException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(Constant.RESOURCE_PATH_PROPERTY)
public class PropertyController {

@Value("${.screens[0].gymRegistration.enabled}")
private String isGymRegistrationEnabled;

private ScreenProperties screenproperties;

public PropertyController(ScreenProperties screenProperties) {
this.screenProperties = screenProperties;
}

@GetMapping(value= {"printProperties"})
public String printProperties() throws MalformedURLException {
System.out.println("Single property via @Value annotation : "+ isGymRegistrationEnabled);
System.out.println("All properties via @ConfigurationProperties annotation : "+ screenProperties.toString());
}
}

Use Case 2: Enabling/disabling a feature via a feature flag

A feature flag is used to enable or disable particular functionality by eliminating boilerplate coding.

Its value is pulled from cloud-config every 30 seconds (default value which can be changed) so that it can be toggled runtime without having our application down.

In the example below, I am toggling the return value (which is in String form) of the method based on the feature flag value.


import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.azure.spring.cloud.feature.management.FeatureManager;

@RestController
@RequestMapping(Constant.RESOURCE_PATH_FEATUE)
public class FeatureController {

private FeatureManager featureManager;

public FeatureController(FeatureManager featureManager) {
this.featureManager = featureManager;
}

@Value("${cloud.config.feature.testFeatureFlag}")
private String testFeatureFlag;

@GetMapping(value= {"/message/","/"})
public String getFeatureMessage() {

featureManager.getAllFeatureNames().forEach(System.out::println);

return (featureManager.getAllFeatureNames().contains(biometricFeatureName))
? (featureManager.isEnabled(biometricFeatureName)) ? "Hey, feature flag is enabled" : "Ooops, feature flag is disabled"
: "Ooops, feature flag is not available in the system" ;
}
}

Use Case 3: CRUD operations for configurations via Spring Boot

To achieve CRUD operation for configurable parameters, Azure provides SDKs.

We can make use of the “ConfigurationClient.class” to achieve different CRUD operations as mentioned below:

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.azure.core.http.rest.PagedIterable;
import com.azure.data.appconfiguration.ConfigurationClient;
import com.azure.data.appconfiguration.ConfigurationClientBuilder;
import com.azure.data.appconfiguration.models.ConfigurationSetting;
import com.azure.data.appconfiguration.models.SettingSelector;

@Service
public class ConfigServiceImpl implements ConfigService{

@Value("${spring.cloud.azure.appconfiguration.stores[0].connection-string}")
private String connectionString;

private ConfigurationClient getConfigClient(String connectionString) {
return new ConfigurationClientBuilder().connectionString(connectionString).buildClient();
}

/*
* Method to fetch all the configured properties from cloud.
*/
@Override
public String fetchCloudConfigurations() {
PagedIterable<ConfigurationSetting> configurationSettingList = getConfigClient(connectionString).listConfigurationSettings(new SettingSelector());
Iterator<ConfigurationSetting> iterator = configurationSettingList.iterator();
Map<String, String> map = new HashMap<String, String>();
while(iterator.hasNext()) {
ConfigurationSetting setting = iterator.next();
System.out.println(String.format("[fetchCloudConfigurations] Key: %s, Value: %s", setting.getKey(), setting.getValue()));
map.put(setting.getKey(), setting.getValue());
}
return map.toString();
}

/*
* Method to create new property in app configuration.
*/
@Override
public String createNewKeyValueInCloudConfig(Config config) {
ConfigurationSetting setting = new ConfigurationSetting();
setting.setContentType(config.contentType());
setting.setKey(config.key());
setting.setLabel(config.label());
setting.setValue(config.value());
setting = getConfigClient(connectionString).addConfigurationSetting(setting);
System.out.println(String.format("[createNewKeyValueInCloudConfig] Key: %s, Value: %s", setting.getKey(), setting.getValue()));
return "Created";
}

/*
* Method to update value of existing property in app configuration.
*/
@Override
public String UpdateExistingValueForGivenKeyInCloudConfig(Config config) {
ConfigurationSetting setting = new ConfigurationSetting();
setting.setContentType(config.contentType());
setting.setKey(config.key());
setting.setLabel(config.label());
setting.setValue(config.value());
setting = getConfigClient(connectionString).setConfigurationSetting(setting);
System.out.println(String.format("[UpdateExistingValueForGivenKeyInCloudConfig] Key: %s, Value: %s", setting.getKey(), setting.getValue()));
return "Updated";
}

/*
* Method to remove particular configuration via its key from app.
*/
@Override
public String removeExistingKeyValueFromCloudConfig(Config config) {
ConfigurationSetting setting = getConfigClient(connectionString).deleteConfigurationSetting(config.key(), config.label());
System.out.println(String.format("[removeExistingKeyValueFromCloudConfig] Key: %s, Value: %s", setting.getKey(), setting.getValue()));
return "Removed";
}
}

Conclusion

There you have it, a few simple steps to integrate Azure Configuration App with your Spring Boot microservice application. Hopefully, there is something useful you can take away from this when you want to have externalized configuration in your Spring Boot application.

Good luck!

--

--