Search results
Refreshing Feature Flags using Togglz and Spring Cloud Config Server
1. OVERVIEW
Your team decided to hide a requirement implementation behind a feature flag using Spring Boot and Togglz.
Now it’s time to switch the toggle state to make the new implementation available. It might also be possible your team needs to switch the flag back if anything goes wrong.
togglz-spring-boot-starter Spring Boot starter’s autoconfigures an instance of FileBasedStateRepository. This requires you to restart your application after changing a toggle value.
You could configure a different StateRepository implementation such as combining JDBCStateRepository or MongoStateRepository with CachingStateRepository to prevent restarting your application.
This blog post helps you with the configuration and implementation of Togglz feature flags to reload new toggles values using Spring Cloud Config Server and Git.
2. APPLICATION DEPENDENCIES
pom.xml
:
<properties>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<togglz.version>2.8.0</togglz.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-spring-boot-starter</artifactId>
<version>${togglz.version}</version>
</dependency>
</dependencies>
Hoxton
is the Spring Cloud release compatible with Spring Boot 2.2.11.RELEASE
.
If you are using a different Spring Boot version, make sure you also use its corresponding Spring Cloud version.
You need spring-cloud-starter-config for this application to read external, Git-backed togglz states via a Spring Cloud Config Server. External configuration helps to prevent changing local togglz configuration, rebuilding, redeploying, and bouncing the application.
3. SPRING CLOUD CLIENT CONFIGURATION
bootstrap.yml
:
spring:
application:
name: springboot2-refresh-togglz
cloud:
config:
uri: http://localhost:8100
We’ll use a local Spring Cloud Config Server listening on 8100
. A file named after this application, springboot2-refresh-togglz.yml
in the Git repository the Configuration Server connects to.
https://bitbucket.org/asimio/demo-config-properties/src/master/springboot2-refresh-togglz.yml has the same togglz flag, togglz.features.USE_NEW_SOMESERVICE.enabled
as application.yml.
4. CONFIGURING FEATURE TOGGLES
application.yml
:
togglz:
# enabled: true # Enabled by default
# featureEnums:
# com.asimio.demo.togglz.config.FeatureToggles
features:
USE_NEW_SOMESERVICE:
enabled: false
FeatureToggles.java
:
public enum FeatureToggles implements Feature {
@Label("New some service.")
USE_NEW_SOMESERVICE;
}
Notice that togglz.features
map includes the key USE_NEW_SOMESERVICE
also defined in the FeatureToggles enum file.
AppConfig.java
:
@Configuration
public class AppConfig {
@Bean
public SomeService oldSomeService() {
return new OldSomeServiceImpl();
}
@Bean
public SomeService newSomeService() {
return new NewSomeServiceImpl();
}
@Bean
public FeatureProxyFactoryBean proxiedSomeService() {
FeatureProxyFactoryBean proxyFactoryBean = new FeatureProxyFactoryBean();
proxyFactoryBean.setFeature(FeatureToggles.USE_NEW_SOMESERVICE.name());
proxyFactoryBean.setProxyType(SomeService.class);
proxyFactoryBean.setActive(this.newSomeService());
proxyFactoryBean.setInactive(this.oldSomeService());
return proxyFactoryBean;
}
@Bean
@Primary
public SomeService someService(@Autowired FeatureProxyFactoryBean proxiedSomeService) throws Exception {
return (SomeService) proxiedSomeService.getObject();
}
...
}
This is the same code as in Adding Feature Toggles to Spring Boot applications using Togglz.
It still doesn’t account for refreshing the relevant @Bean-annotated methods.
Adding @RefreshScope to the @Bean proxiedSomeService() method prevents the application from starting. It fails to autowire a required bean.
Caused by: java.lang.IllegalStateException: Cannot create scoped proxy for bean 'scopedTarget.proxiedSomeService': Target type could not be determined at the time of proxy creation.
Adding @RefreshScope to the @Bean someService() method doesn’t refresh the right bean.
4.1. CONFIGURING TOGGLZ’s StateRepository
Going through togglz-spring-boot-starter source code I found TogglzAutoConfiguration$StateRepositoryConfiguration.stateRepository().
You just need to borrow the same implementation and add the @RefreshScope annotation.
AppConfig.java
: (Contd):
@Configuration
public class AppConfig {
@Autowired
private TogglzProperties togglzProperties;
@Autowired
private ResourceLoader resourceLoader;
@Bean
@RefreshScope
public StateRepository stateRepository() throws IOException {
StateRepository stateRepository;
String featuresFile = this.togglzProperties.getFeaturesFile();
if (featuresFile != null) {
Resource resource = this.resourceLoader.getResource(featuresFile);
Integer minCheckInterval = this.togglzProperties.getFeaturesFileMinCheckInterval();
if (minCheckInterval != null) {
stateRepository = new FileBasedStateRepository(resource.getFile(), minCheckInterval);
} else {
stateRepository = new FileBasedStateRepository(resource.getFile());
}
} else {
Map<String, FeatureSpec> features = this.togglzProperties.getFeatures();
stateRepository = new InMemoryStateRepository();
for (String name : features.keySet()) {
stateRepository.setFeatureState(features.get(name).state(name));
}
}
// If caching is enabled wrap state repository in caching state repository.
if (this.togglzProperties.getCache().isEnabled()) {
stateRepository = new CachingStateRepository(
stateRepository,
this.togglzProperties.getCache().getTimeToLive(),
this.togglzProperties.getCache().getTimeUnit()
);
}
return stateRepository;
}
...
TogglzAutoConfiguration gets out of the way and allows us to provide our own StateRepository bean.
5. USING FEATURE TOGGLES
- Send a request to the application results in using the legacy service class implementation.
curl http://localhost:8080/api/demo-someservice
Value from old service implementation
- Enable the
USE_NEW_SOMESERVICE
feature flag found in the Git repository at https://bitbucket.org/asimio/demo-config-properties/src/master/springboot2-refresh-togglz.yml:
togglz:
features:
USE_NEW_SOMESERVICE:
- enabled: false
+ enabled: true
- Refresh the application to reload the new value and to refresh the impacted beans:
curl -v -H "Content-Type: application/json" localhost:8080/actuator/refresh -d {}
["config.client.version","togglz.features.USE_NEW_SOMESERVICE.enabled"]
- Resend same request now results in switching to new service class implementation.
curl http://localhost:8080/api/demo-someservice
Value from new service implementation
6. CONCLUSION
This blog post helps you to dynamically switch between feature flag states without the need to restart your application using Togglz and Spring Cloud Config Server.
togglz-spring-boot-starter gets out of the way and allows you to provide your own StateRepository bean to switch your implementation toggle without bouncing your application.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
7. SOURCE CODE
Accompanying source code for this blog post can be found at:
- https://bitbucket.org/asimio/springboot2-refresh-togglz
- https://bitbucket.org/asimio/demo-config-properties/src/master/springboot2-refresh-togglz.yml
8. REFERENCES
NEED HELP?
I provide Consulting Services.ABOUT THE AUTHOR
