Skip to main content

Spring Cloud Config

Introduction

When working with microservices, managing configuration across multiple services and environments becomes a challenging task. Rather than building configuration management into each service, Spring Cloud Config provides a centralized way to manage external properties for applications across all environments.

Spring Cloud Config offers a server and client-side support for externalized configuration in a distributed system. With the Config Server, you have a central place to manage external properties for applications across all environments. The concepts are similar to the Spring Environment and PropertySource abstractions, but with special handling for version-controlled configuration and service applications that need to start up with defaults when not connected to the Config Server.

Why Use Spring Cloud Config?

Before diving into the implementation, let's understand why Spring Cloud Config is valuable:

  • Centralized management: Store all configurations in one place
  • Dynamic updates: Change configurations without restarting applications
  • Environment segregation: Maintain separate configurations for development, testing, and production
  • Version control: Track configuration changes with Git
  • Security: Secure sensitive configuration data

Getting Started with Spring Cloud Config

Let's break down how to implement Spring Cloud Config in two main components:

  1. Config Server - stores and serves configurations
  2. Config Client - connects to the server to retrieve configurations

Setting Up Config Server

First, let's create a Spring Cloud Config Server:

Step 1: Create a new Spring Boot project

Create a new Spring Boot application with Spring Cloud Config Server dependency.

xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

Step 2: Enable Config Server in your main application class

java
package com.example.configserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

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

Step 3: Configure the Config Server

In your application.properties (or application.yml) file:

properties
server.port=8888
spring.cloud.config.server.git.uri=https://github.com/your-username/config-repo
spring.cloud.config.server.git.default-label=main

Or using YAML:

yaml
server:
port: 8888
spring:
cloud:
config:
server:
git:
uri: https://github.com/your-username/config-repo
default-label: main

This configures the server to run on port 8888 and fetch configurations from your Git repository.

Setting Up the Config Repository

Now, let's set up a Git repository to store our configuration files:

  1. Create a new Git repository (e.g., on GitHub)
  2. Add application configuration files following the naming convention:
{application-name}.properties
{application-name}-{profile}.properties
application.properties (for shared configurations)

For example, if you have a service called "order-service", you might create:

  • order-service.properties - default configuration
  • order-service-dev.properties - development environment configuration
  • order-service-prod.properties - production environment configuration

Here's a simple example of what these files might contain:

order-service.properties:

properties
app.description=Order Processing Service
app.max-orders=500

order-service-dev.properties:

properties
server.port=8081
logging.level.root=DEBUG

order-service-prod.properties:

properties
server.port=8082
logging.level.root=INFO
app.max-orders=10000

Setting Up Config Client

Now, let's create a client application that will retrieve its configuration from our Config Server:

Step 1: Add dependencies to your client application

xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

Step 2: Configure the client to use the Config Server

Create a bootstrap.properties (or bootstrap.yml) file in your resources folder:

properties
spring.application.name=order-service
spring.cloud.config.uri=http://localhost:8888

Or using YAML:

yaml
spring:
application:
name: order-service
cloud:
config:
uri: http://localhost:8888
note

Starting from Spring Cloud 2020.0.0, you need to add the spring-cloud-starter-bootstrap dependency when using bootstrap.properties, or you can use application.properties with additional configuration.

Step 3: Access the configuration properties in your application

java
package com.example.orderservice;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class OrderServiceApplication {

@Value("${app.description:Default Order Service}")
private String appDescription;

@Value("${app.max-orders:100}")
private Integer maxOrders;

public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}

@GetMapping("/service-info")
public String getServiceInfo() {
return "Service: " + appDescription + ", Max Orders: " + maxOrders;
}
}

Dynamic Configuration Refresh

One of the powerful features of Spring Cloud Config is the ability to change configurations on the fly without restarting your applications.

Step 1: Add the Actuator dependency to your client

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Step 2: Enable the refresh endpoint in your client's properties

properties
management.endpoints.web.exposure.include=refresh

Step 3: Add the @RefreshScope annotation to your classes that use configuration

java
package com.example.orderservice;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RefreshScope
public class ConfigController {

@Value("${app.description:Default Order Service}")
private String appDescription;

@Value("${app.max-orders:100}")
private Integer maxOrders;

@GetMapping("/service-info")
public String getServiceInfo() {
return "Service: " + appDescription + ", Max Orders: " + maxOrders;
}
}

Step 4: Refresh configurations

When you update your configuration in the Git repository, you can refresh your client application by making a POST request to the refresh endpoint:

POST http://localhost:8081/actuator/refresh

The response will show which properties have been updated:

json
["app.max-orders"]

Security Considerations

In production environments, you'll want to secure your Config Server. Here's how to add basic security:

Step 1: Add Spring Security dependency to the Config Server

xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

Step 2: Configure security settings

properties
spring.security.user.name=configuser
spring.security.user.password=configpassword

Step 3: Update client to use authentication

properties
spring.cloud.config.username=configuser
spring.cloud.config.password=configpassword

Real-World Example: Multi-Environment Configuration

Let's build a more comprehensive example that demonstrates how Spring Cloud Config can be used to manage configurations across multiple environments:

Configuration Repository Structure

├── application.properties        # Shared properties for all services
├── order-service.properties # Default properties for order service
├── order-service-dev.properties # Dev environment properties
├── order-service-qa.properties # QA environment properties
├── order-service-prod.properties # Production environment properties
├── payment-service.properties # Default properties for payment service
└── ...

Common application.properties

properties
# Common settings for all services
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
spring.jpa.hibernate.ddl-auto=update
management.endpoints.web.exposure.include=health,info,metrics,refresh
app.common.cache-timeout=3600

Order Service Configuration

order-service.properties:

properties
# Default order service settings
app.order.process-timeout=30000
app.order.max-items-per-order=50
server.tomcat.max-threads=200

order-service-dev.properties:

properties
# Dev-specific settings
server.port=8081
logging.level.com.example=DEBUG
spring.datasource.url=jdbc:mysql://localhost:3306/ordersdb_dev
spring.datasource.username=devuser
spring.datasource.password=devpass
app.order.process-timeout=60000

order-service-prod.properties:

properties
# Production-specific settings
server.port=8081
logging.level.com.example=INFO
spring.datasource.url=jdbc:mysql://prod-db:3306/ordersdb
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASSWORD}
server.tomcat.max-threads=800
app.order.max-items-per-order=100

Order Service Client Implementation

java
package com.example.orderservice.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "app.order")
public class OrderServiceConfig {

private int processTimeout;
private int maxItemsPerOrder;

// Getters and setters
public int getProcessTimeout() {
return processTimeout;
}

public void setProcessTimeout(int processTimeout) {
this.processTimeout = processTimeout;
}

public int getMaxItemsPerOrder() {
return maxItemsPerOrder;
}

public void setMaxItemsPerOrder(int maxItemsPerOrder) {
this.maxItemsPerOrder = maxItemsPerOrder;
}
}
java
package com.example.orderservice.controller;

import com.example.orderservice.config.OrderServiceConfig;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/orders/config")
public class OrderConfigController {

@Autowired
private OrderServiceConfig orderConfig;

@Value("${app.common.cache-timeout:1800}")
private int cacheTimeout;

@Value("${spring.datasource.url:unknown}")
private String datasourceUrl;

@GetMapping
public Map<String, Object> getConfigInfo() {
Map<String, Object> config = new HashMap<>();
config.put("processTimeout", orderConfig.getProcessTimeout());
config.put("maxItemsPerOrder", orderConfig.getMaxItemsPerOrder());
config.put("cacheTimeout", cacheTimeout);
config.put("datasourceUrl", datasourceUrl);
return config;
}
}

To run the order service in different environments, you'd set the active profile:

# For development
java -jar order-service.jar --spring.profiles.active=dev

# For production
java -jar order-service.jar --spring.profiles.active=prod

Using Spring Cloud Config with Spring Cloud Bus

For large deployments with many client applications, refreshing each client individually becomes impractical. Spring Cloud Bus links your services together with a lightweight message broker and lets you broadcast configuration changes.

Step 1: Add dependencies to your client applications

xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

Step 2: Configure RabbitMQ in your client applications

properties
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

Step 3: Enable bus-refresh endpoint

properties
management.endpoints.web.exposure.include=bus-refresh

Step 4: Broadcast configuration changes

When you update your configuration in the Git repository, you can broadcast the changes to all connected clients by making a POST request to any one of the clients:

POST http://localhost:8081/actuator/bus-refresh

This will notify all applications connected to the message broker to refresh their configurations.

Summary

Spring Cloud Config provides a powerful way to manage configurations for microservice applications:

  1. Config Server centralizes configuration storage and servicing
  2. Git integration enables version control and history of configurations
  3. Environment-specific configurations allow for differences between dev, test, and prod
  4. Dynamic refreshing updates configurations without application restarts
  5. Spring Cloud Bus enables efficient configuration updates across many services

By using Spring Cloud Config, you can manage application configurations more effectively, especially in complex microservice architectures with multiple environments and services.

Additional Resources

Exercises

  1. Set up a Spring Cloud Config Server that retrieves configuration from a local Git repository.
  2. Create a client application that reads configuration from your Config Server.
  3. Implement the @RefreshScope annotation and test dynamic configuration updates.
  4. Set up multiple environment-specific configuration files and test switching between them.
  5. Advanced: Implement encryption/decryption of sensitive properties in your configuration files.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)