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.

Switching Togglz flags in Spring Boot applications without restarting them

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
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:

8. REFERENCES