google-site-verification: google61fe8ba583a51912.html
Distributed Configuration Management in Java

Distributed configuration management is essential for modern cloud-native applications to manage configuration across multiple services, environments, and instances consistently. This guide covers various approaches and tools for distributed configuration management in Java applications.

1. Spring Cloud Config

Server Configuration

Config Server Dependencies

<!-- pom.xml --> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2022.0.4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>

Config Server Application

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

application.yml - Config Server

server: port: 8888 spring: application: name: config-server security: user: name: admin password: ${CONFIG_SERVER_PASSWORD:secret} cloud: config: server: git: uri: https://github.com/my-org/config-repo search-paths: '{application}' default-label: main timeout: 10 force-pull: true native: search-locations: classpath:/shared management: endpoints: web: exposure: include: health,info,refresh,bus-refresh endpoint: health: show-details: always encrypt: key: ${CONFIG_ENCRYPT_KEY:default-encryption-key}

Client Configuration

Client Dependencies

<dependencies> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>

bootstrap.yml - Client

spring: application: name: user-service profiles: active: ${SPRING_PROFILES_ACTIVE:development} cloud: config: uri: http://config-server:8888 username: admin password: ${CONFIG_SERVER_PASSWORD:secret} fail-fast: true retry: initial-interval: 1000 max-interval: 2000 max-attempts: 6

Configuration Client Class

@RestController @RefreshScope public class UserController { @Value("${app.feature.flags.new-registration:false}") private boolean newRegistrationFeature; @Value("${app.database.connection.pool.size:10}") private int connectionPoolSize; @Value("${app.notification.email.enabled:true}") private boolean emailEnabled; @Autowired private ApplicationConfig applicationConfig; @GetMapping("/config") public Map<String, Object> getConfig() { return Map.of( "newRegistrationFeature", newRegistrationFeature, "connectionPoolSize", connectionPoolSize, "emailEnabled", emailEnabled, "maxUsers", applicationConfig.getMaxUsers() ); } } @Component @ConfigurationProperties(prefix = "app") public class ApplicationConfig { private int maxUsers = 1000; private RateLimit rateLimit = new RateLimit(); private Cache cache = new Cache(); // Getters and setters public static class RateLimit { private int requestsPerMinute = 100; private boolean enabled = true; // Getters and setters } public static class Cache { private int ttlSeconds = 300; private int maxSize = 10000; // Getters and setters } }

2. Consul for Configuration Management

Consul Client Setup

Dependencies

<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> </dependencies>

Consul Configuration

@Configuration public class ConsulConfig { @Bean public ConsulConfigProperties consulConfigProperties() { ConsulConfigProperties properties = new ConsulConfigProperties(); properties.setFailFast(true); properties.setPrefix("config"); return properties; } }

bootstrap.yml for Consul

spring: application: name: order-service cloud: consul: host: ${CONSUL_HOST:localhost} port: ${CONSUL_PORT:8500} config: enabled: true format: YAML prefix: config default-context: application profile-separator: '-' data-key: data watch: wait-time: 55 enabled: true discovery: instance-id: ${spring.application.name}:${random.value} health-check-path: /actuator/health health-check-interval: 30s

Consul Configuration Watcher

@Component public class ConsulConfigWatcher { private static final Logger logger = LoggerFactory.getLogger(ConsulConfigWatcher.class); @Autowired private ConfigurableApplicationContext applicationContext; @EventListener public void handleConfigUpdate(EnvironmentChangeEvent event) { logger.info("Configuration changed: {}", event.getKeys()); // Refresh specific beans applicationContext.getBeanFactory().destroySingleton("refreshableBean"); // Perform custom logic based on changed keys event.getKeys().forEach(key -> { switch (key) { case "app.feature.flags.new-payment": logger.info("New payment feature flag updated"); break; case "app.database.connection.pool.size": logger.info("Database connection pool size updated"); break; } }); } }

3. ZooKeeper Configuration Management

ZooKeeper Setup

<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zookeeper-config</artifactId> </dependency> </dependencies>

ZooKeeper Configuration Client

@Configuration public class ZookeeperConfig { @Bean @ConditionalOnMissingBean public CuratorFramework curatorFramework(ZookeeperConfigProperties properties) { CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder(); RetryPolicy retryPolicy = new ExponentialBackoffRetry( properties.getBaseSleepTimeMs(), properties.getMaxRetries() ); CuratorFramework curator = builder .connectString(properties.getConnectString()) .retryPolicy(retryPolicy) .namespace(properties.getNamespace()) .build(); curator.start(); return curator; } } @Component public class ZookeeperConfigManager { private final CuratorFramework curator; private final ObjectMapper objectMapper; public ZookeeperConfigManager(CuratorFramework curator, ObjectMapper objectMapper) { this.curator = curator; this.objectMapper = objectMapper; } public void saveConfig(String path, Object config) throws Exception { byte[] data = objectMapper.writeValueAsBytes(config); if (curator.checkExists().forPath(path) == null) { curator.create().creatingParentsIfNeeded().forPath(path, data); } else { curator.setData().forPath(path, data); } } public <T> T getConfig(String path, Class<T> clazz) throws Exception { if (curator.checkExists().forPath(path) == null) { return null; } byte[] data = curator.getData().forPath(path); return objectMapper.readValue(data, clazz); } public void watchConfig(String path, ConfigWatcher watcher) throws Exception { NodeCache nodeCache = new NodeCache(curator, path); nodeCache.getListenable().addListener(() -> { ChildData currentData = nodeCache.getCurrentData(); if (currentData != null) { byte[] data = currentData.getData(); watcher.onConfigChanged(data); } }); nodeCache.start(); } public interface ConfigWatcher { void onConfigChanged(byte[] newData); } }

4. Environment-Specific Configuration

Multi-Environment Setup

application-development.yml

app: name: "User Service - Development" environment: "development" database: url: "jdbc:postgresql://localhost:5432/users_dev" username: "dev_user" pool-size: 5 feature: flags: new-registration: true email-verification: false cache: enabled: true ttl: 300 rate-limit: enabled: false requests-per-minute: 1000 logging: level: com.example: DEBUG org.springframework: INFO

application-production.yml

app: name: "User Service - Production" environment: "production" database: url: "jdbc:postgresql://prod-db-cluster:5432/users_prod" username: ${DB_USERNAME} password: ${DB_PASSWORD} pool-size: 20 feature: flags: new-registration: true email-verification: true cache: enabled: true ttl: 900 rate-limit: enabled: true requests-per-minute: 100 logging: level: com.example: INFO org.springframework: WARN management: endpoints: web: exposure: include: health,metrics,info endpoint: health: show-details: when_authorized

5. Dynamic Configuration with Feature Flags

Feature Flag Management

@Component @RefreshScope public class FeatureFlagManager { @Value("${app.feature.flags.new-registration:false}") private boolean newRegistrationEnabled; @Value("${app.feature.flags.payment-v2:false}") private boolean paymentV2Enabled; @Value("${app.feature.flags.experimental-ui:false}") private boolean experimentalUiEnabled; private final Map<String, Boolean> featureFlags = new ConcurrentHashMap<>(); @PostConstruct public void init() { featureFlags.put("new-registration", newRegistrationEnabled); featureFlags.put("payment-v2", paymentV2Enabled); featureFlags.put("experimental-ui", experimentalUiEnabled); } @EventListener public void onConfigUpdate(EnvironmentChangeEvent event) { if (event.getKeys().stream().anyMatch(key -> key.startsWith("app.feature.flags"))) { refreshFeatureFlags(); } } private void refreshFeatureFlags() { // This method will be called when configuration is refreshed // In a real implementation, you would re-read the values } public boolean isEnabled(String feature) { return featureFlags.getOrDefault(feature, false); } public Map<String, Boolean> getAllFlags() { return new HashMap<>(featureFlags); } } @RestController @RequestMapping("/api/features") public class FeatureFlagController { private final FeatureFlagManager featureFlagManager; public FeatureFlagController(FeatureFlagManager featureFlagManager) { this.featureFlagManager = featureFlagManager; } @GetMapping public Map<String, Boolean> getFeatureFlags() { return featureFlagManager.getAllFlags(); } @GetMapping("/{feature}") public boolean isFeatureEnabled(@PathVariable String feature) { return featureFlagManager.isEnabled(feature); } }

6. Configuration Encryption

Jasypt Encryption Setup

<dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.5</version> </dependency>

Encryption Configuration

@Component public class ConfigurationEncryptor { @Value("${jasypt.encryptor.password}") private String encryptionPassword; public String encrypt(String value) { StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); encryptor.setPassword(encryptionPassword); return "ENC(" + encryptor.encrypt(value) + ")"; } public String decrypt(String encryptedValue) { if (encryptedValue.startsWith("ENC(") && encryptedValue.endsWith(")")) { String actualValue = encryptedValue.substring(4, encryptedValue.length() - 1); StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor(); encryptor.setPassword(encryptionPassword); return encryptor.decrypt(actualValue); } return encryptedValue; } } @Configuration public class EncryptionConfig { @Bean public StringEncryptor jasyptStringEncryptor() { PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); SimpleStringPBEConfig config = new SimpleStringPBEConfig(); config.setPassword(System.getenv("JASYPT_ENCRYPTOR_PASSWORD")); config.setAlgorithm("PBEWITHHMACSHA512ANDAES_256"); config.setKeyObtentionIterations("1000"); config.setPoolSize("1"); config.setProviderName("SunJCE"); config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator"); config.setStringOutputType("base64"); encryptor.setConfig(config); return encryptor; } }

Encrypted Properties Example

# application.yml with encrypted values app: database: password: "ENC(AbCdEfGhIjKlMnOpQrStUvWxYz123456==)" api: key: "ENC(XyZaBcDeFgHiJkLmNoPqRsTuVwXyZ987==)" external: service: secret: "ENC(MnOpQrStUvWxYzAbCdEfGhIjKlMnOp12==)" jasypt: encryptor: bean: jasyptStringEncryptor

7. Configuration Validation

Configuration Validation Setup

@Configuration @ConfigurationProperties(prefix = "app") @Validated public class AppConfig { @NotNull @NotEmpty private String name; @Valid private DatabaseConfig database; @Valid private SecurityConfig security; @Min(1) @Max(100) private int maxConnections; // Getters and setters @AssertTrue(message = "Database configuration must be valid") public boolean isDatabaseConfigValid() { return database != null && database.isValid(); } public static class DatabaseConfig { @NotBlank private String url; @NotBlank private String username; private String password; @Min(1) @Max(50) private int poolSize; public boolean isValid() { return url != null && !url.trim().isEmpty(); } // Getters and setters } public static class SecurityConfig { private boolean sslEnabled; @Pattern(regexp = "^(TLSv1\\.2|TLSv1\\.3)$") private String sslProtocol = "TLSv1.2"; // Getters and setters } } @Component public class ConfigValidator { private final Validator validator; public ConfigValidator(Validator validator) { this.validator = validator; } public void validateConfig(AppConfig config) { Set<ConstraintViolation<AppConfig>> violations = validator.validate(config); if (!violations.isEmpty()) { List<String> errorMessages = violations.stream() .map(v -> v.getPropertyPath() + ": " + v.getMessage()) .collect(Collectors.toList()); throw new IllegalStateException("Configuration validation failed: " + String.join(", ", errorMessages)); } } @EventListener public void validateOnRefresh(EnvironmentChangeEvent event) { // Re-validate configuration on refresh } }

8. Distributed Configuration Best Practices

Configuration Health Check

@Component public class ConfigurationHealthIndicator implements HealthIndicator { private final Environment environment; private final AppConfig appConfig; public ConfigurationHealthIndicator(Environment environment, AppConfig appConfig) { this.environment = environment; this.appConfig = appConfig; } @Override public Health health() { try { // Check if required properties are set checkRequiredProperties(); // Validate configuration validateConfiguration(); return Health.up() .withDetail("configServer", "connected") .withDetail("propertiesLoaded", true) .build(); } catch (Exception e) { return Health.down(e) .withDetail("error", "Configuration validation failed") .build(); } } private void checkRequiredProperties() { String[] requiredProps = { "app.database.url", "app.database.username", "spring.application.name" }; for (String prop : requiredProps) { if (!environment.containsProperty(prop)) { throw new IllegalStateException("Required property missing: " + prop); } } } private void validateConfiguration() { // Custom validation logic if (appConfig.getMaxConnections() < 1) { throw new IllegalStateException("maxConnections must be at least 1"); } } }

Configuration Monitoring

@Component public class ConfigurationMetrics { private final MeterRegistry meterRegistry; private final Environment environment; private final Counter configUpdateCounter; private final Gauge configValueGauge; public ConfigurationMetrics(MeterRegistry meterRegistry, Environment environment) { this.meterRegistry = meterRegistry; this.environment = environment; this.configUpdateCounter = Counter.builder("config.updates") .description("Number of configuration updates") .register(meterRegistry); // Register gauges for important configuration values setupConfigurationGauges(); } @EventListener public void onConfigUpdate(EnvironmentChangeEvent event) { configUpdateCounter.increment(); // Update gauges with new values updateConfigurationGauges(); } private void setupConfigurationGauges() { Gauge.builder("config.database.pool.size") .description("Database connection pool size") .tag("application", environment.getProperty("spring.application.name")) .register(meterRegistry, this, metrics -> Integer.parseInt(environment.getProperty("app.database.pool.size", "10"))); } private void updateConfigurationGauges() { // Force gauge refresh meterRegistry.forEachMeter(Meter::measure); } }

This comprehensive guide covers distributed configuration management in Java using various tools and patterns, providing a solid foundation for managing configuration in distributed systems effectively.

Leave a Reply

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


Macro Nepal Helper