Configurations Management in Microservices


How to Read Properties in Spring Boot

Spring Boot provides multiple ways to read properties from configuration files like application.properties or application.yml. These properties can be accessed in your application using various approaches depending on the use case.

1. Using @Value

The @Value annotation is used to inject a single property value into a field.

@RestController
    public class MyController {
    
        @Value("${app.name}")
        private String appName;
    
        @GetMapping("/app-name")
        public String getAppName() {
            return appName;
        }
    }
    

Explanation: The @Value annotation injects the value of app.name from application.properties.

# application.properties
    app.name=My Spring Boot App
    
2. Using @ConfigurationProperties

The @ConfigurationProperties annotation binds a group of properties into a Java class.

@Component
    @ConfigurationProperties(prefix = "app")
    public class AppConfig {
        private String name;
        private String version;
    
        // Getters and Setters
    }
    
    @RestController
    public class MyController {
    
        private final AppConfig appConfig;
    
        public MyController(AppConfig appConfig) {
            this.appConfig = appConfig;
        }
    
        @GetMapping("/app-info")
        public String getAppInfo() {
            return "Name: " + appConfig.getName() + ", Version: " + appConfig.getVersion();
        }
    }
    

Explanation: The @ConfigurationProperties binds all properties with the prefix app into the AppConfig class.

# application.properties
    app.name=My Spring Boot App
    app.version=1.0.0
    
3. Using Environment Object

The Environment object can be used to programmatically fetch property values.

@RestController
    public class MyController {
    
        private final Environment environment;
    
        public MyController(Environment environment) {
            this.environment = environment;
        }
    
        @GetMapping("/app-description")
        public String getAppDescription() {
            return environment.getProperty("app.description");
        }
    }
    

Explanation: The Environment object retrieves the value of app.description at runtime.

# application.properties
    app.description=This is a Spring Boot application.
    
4. Using @PropertySource Annotation

The @PropertySource annotation is used to load properties from an external file.

@Configuration
    @PropertySource("classpath:custom.properties")
    public class CustomConfig {
    
        @Value("${custom.property}")
        private String customProperty;
    
        public String getCustomProperty() {
            return customProperty;
        }
    }
    
    @RestController
    public class MyController {
    
        private final CustomConfig customConfig;
    
        public MyController(CustomConfig customConfig) {
            this.customConfig = customConfig;
        }
    
        @GetMapping("/custom-property")
        public String getCustomProperty() {
            return customConfig.getCustomProperty();
        }
    }
    

Explanation: The @PropertySource annotation loads properties from the custom.properties file.

# custom.properties
    custom.property=Custom Value
    
5. Using @Configuration and Beans

Properties can also be loaded and injected into beans.

@Configuration
    public class AppConfig {
    
        @Bean
        public String appName(@Value("${app.name}") String appName) {
            return appName;
        }
    }
    
    @RestController
    public class MyController {
    
        private final String appName;
    
        public MyController(String appName) {
            this.appName = appName;
        }
    
        @GetMapping("/bean-app-name")
        public String getBeanAppName() {
            return appName;
        }
    }
    

Explanation: This approach uses a bean to inject the property value, which can then be used in the application.

Conclusion

Each method has its own use case. Use @Value for single properties, @ConfigurationProperties for grouped properties, and Environment for programmatic access. The @PropertySource annotation and Bean configuration provide additional flexibility for loading and injecting properties.


Step 1: Setting Up the Configurations

You're developing a microservice called Account Service using Spring Boot. The application needs to be deployed across multiple environments (dev, qa, prod) with different configurations. The goal is to:

To address these challenges, we'll use Spring Boot profiles and externalize configurations:

Environment-Specific Configuration Files

Create application.yml for common settings and environment-specific files (application-dev.yml, application-qa.yml, application-prod.yml) for custom properties.

Example:
application.yml:

spring:
  application:
    name: account-service
logging:
  level:
    root: INFO
application-dev.yml:

datasource:
  url: jdbc:mysql://dev-db:3306/accounts
  username: dev_user
application-qa.yml:

datasource:
  url: jdbc:mysql://qa-db:3306/accounts
  username: qa_user
application-prod.yml:

datasource:
  url: jdbc:mysql://prod-db:3306/accounts
  username: prod_user
 

Secure Database Credentials

Use AWS Secrets Manager to securely store sensitive data like database passwords.

Centralized Configuration Management

Use Spring Cloud Config Server to pull properties from a Git repository:

Repository Structure:

config-repo/
  account-service-dev.yml
  account-service-qa.yml
  account-service-prod.yml
                

Step 2: Building a Flexible CI/CD Pipeline

Now, let's set up a Jenkins pipeline to automate builds and deployments:

Configuring Jenkins for Parameterized Deployment

Create a parameterized Jenkins job with a dropdown to select the target environment:


parameters {
    choice(name: 'ENVIRONMENT', choices: ['dev', 'qa', 'prod'], description: 'Select the deployment environment')
}
                

Setting the Profile Dynamically


environment {
    SPRING_PROFILES_ACTIVE = "${params.ENVIRONMENT}"
}
                

Pipeline Stages

Checkout Code:

stage('Checkout Code') {
    steps {
        checkout scm
    }
}
                
Build the Application:

stage('Build') {
    steps {
        sh 'mvn clean package'
    }
}
                
Deploy the Application:

stage('Deploy') {
    steps {
        sh "java -Dspring.profiles.active=${SPRING_PROFILES_ACTIVE} -jar target/account-service.jar"
    }
}
                

Step 3: Deploying Across Environments

When the pipeline is executed:

  1. The user selects the target environment (e.g., qa) from the Jenkins job dropdown.
  2. Jenkins sets SPRING_PROFILES_ACTIVE to qa and starts the deployment process.
  3. During the application startup, Spring Boot loads environment-specific properties and fetches sensitive credentials from AWS Secrets Manager.

Step 4: Handling Secrets Securely

To handle sensitive credentials securely:


@Configuration
public class SecretsConfig {
    @Bean
    public String dbPassword() {
        AWSSecretsManager secretsManager = AWSSecretsManagerClientBuilder.standard()
            .withRegion("us-east-1")
            .build();
        String secretValue = secretsManager.getSecretValue(new GetSecretValueRequest()
            .withSecretId("account-service-" + System.getProperty("spring.profiles.active") + "-db-creds"))
            .getSecretString();
        return new JSONObject(secretValue).getString("password");
    }
}
            

Step 5: Deployment on Kubernetes

For Kubernetes deployment:


env:
- name: SPRING_PROFILES_ACTIVE
  value: "qa"
            

Step 6: Refreshing Configurations at Runtime

To allow the application to refresh its configurations dynamically without redeploying:

Spring Cloud Bus Setup:

@SpringBootApplication
@EnableConfigServer
@EnableDiscoveryClient
@EnableAutoConfiguration
public class ConfigServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}
            
Spring Cloud Bus Configuration:

@SpringBootApplication
@EnableSpringCloudBus
@EnableConfigServer
@EnableDiscoveryClient
public class AccountServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AccountServiceApplication.class, args);
    }
}
            
Application.yml for Config Refresh:

spring:
  cloud:
    config:
      monitor:
        enabled: true
      bus:
        refresh:
          applications: account-service
            
Runtime Refresh Controller:

@RestController
public class ConfigRefreshController {

    private final ConfigurableApplicationContext context;

    @Autowired
    public ConfigRefreshController(ConfigurableApplicationContext context) {
        this.context = context;
    }

    @GetMapping("/refresh")
    public ResponseEntity refreshConfig() {
        context.publishEvent(new RefreshEvent(this));
        return ResponseEntity.ok("Configuration refreshed!");
    }
}
            

Triggering /refresh Endpoint Automatically

The /refresh endpoint can be triggered manually via a Jenkins job or automatically upon detecting changes in the Git repository monitored by Spring Cloud Config Monitor:

Climax: Automated and Flexible Deployment

With the pipeline in place:

Resolution: Seamless CI/CD Workflow

The team now enjoys: