Feature toggle in Java Spring

Today I have an hard time using the @Profile directive for enable feature toggle on Spring, so I decided to write a small guide on it.

Introduction

Feature toggle is a way to write your code to be able to 'turn' on/off specific modules of your (micro) service. Because Spring Autowiring can 'discover' the right service for the right need, you can easily introduce feature toggle in an Inversion of Control engine like Spring.
I am not a super fan of feature toggle, but it helped me on more than one project. Also sometimes on production delivery you have some legal constraints (like enable a service not before a specifica date) and so a dynamic, parametrized run become a must.

First of all, consider how configuration should work. My humble opinion is that configuration should be not related to deployment environment. I mean, you have a 'PRODUCTION' configuration but you can have more than one PRODUCTION environment. This is a winning move because in this way you can reconfigure a cluster for other environment easily: move UAT config on SIT to get a second UAT environment, for instance.

So you should have three pieces: the java code, the configuration (stored independently from it) and the target environment (i.e. UAT).

You must be able to deploy the same code on a different environment, often with a different configuration.

The easiest way to get it is to have spring configuration file which load its parameters from environment variables; for critical variable you will refrain to have defaults (i.e. DB_PASSWORD), where for others you can use dev defaults (i.e. concurrent level):

app.password=${PASSWORD}
app.concurrent.min_level=$(APP_THREAD_MIN:2}

Another need is the ability to test every configuration variant you are going to introduce: a special annotation called @TestPropertySource will do the trick.

The requirement

  1. You need to be able to run the same code base under different configuration.
    So you need NOT ship different spring profiles on the same jar!
  2. The only difference between two environments picked randomly must be in the environment variables (NOT the property files!)
  3. The system must autowire at boot with the proper specializations.
  4. The system must be unable to boot if there are no the correct implementation, or if the required variables are not in place
  5. Sensible default must be enabled to avoid requiring developers to set up too much environment variable. Bonus: sometimes I disable heavy services by default to speed up boot on development
  6. You must be able to write unit tests for every configuration, if you need to

Magic ingredients used with care

Spring provides

  1. @ConditionalOnProperty and @ConditionalOnExpression
    annotations inside the org.springframework.boot.autoconfigure.condition
    This package provide a lot of variants
  2. JUnit 5 @EnabledIfSystemProperty / @EnabledOnOs etc for excluding specific tests if needed.
    Also use @TestPropertySource to pick the correct test configuration if you need to modify some defaults
  3. Autowiring and IoC engine you must use in the proper way
  4. @Configuration classes if you have very special needs

So the basic idea is to:

  1. Create a NiceService interface
  2. Create the 2+ Implementations you need, for instance NiceServiceIT, NiceServiceDE, NiceServiceFR
  3. Use the NiceService interface as @Autowired where you need it.
  4. On top of every implementation put a proper @ConditionalOnExpression to set the 'trigger' condition. For very easy use case the @ConditionalOnProperty could be enough

For unit testing, I like the idea of a DummyPlug (inspired by NG Evangelion anime :) so lets' introduce it.
A dummyplug is a service which normally does nothing, or worst send exception all the way around. You can decorate it with a @ConditionalOnProperty to get it working only during the test profile.
The idea is the unit test uses Mockito to 'mock' the plug and get the test done, without any integration needed.
This approach is good because if you forgot to mock an integration with mockito, you got a fatal error instead of a real connection to a test service(!)

A word about Unit tests and launch line

Unit tests should run on every major environment, so after some initial idea, I refrain to use @TestPropertySource to change property file sets, because it can lead to duplicating test properties and major issue.
I forced a major system property ("tenant") to be always present: if it is not present, spring cannot select the correct auto-wiring set and breaks at boot. There are two properties you can define inside maven <properties> block to do this: below an example

<spring-boot.run.jvmArguments>-Dtenant=IT -Duser.timezone=Europe/Rome -Dfile.encoding=UTF-8 -verbose:gc -client -Xms200m -Xmx640m</spring-boot.run.jvmArguments>
<!-- Used to properly configure unit tests with surefire-->
<argLine>-Dcountry_tenant=IT -verbose:gc -client</argLine>

argLine is a magical property of JUnit5 you can use for configuring your test tenant and change it if needed.

You can customize these two properties using spring profile, and increase the complexity of the overall project to infinite level :)

Ending word: test twice, commit one

This solution increase the cognitive complexity because you are replacing direct service implementation with interface, and only the runtime can tell you EXACTLY the type of the concrete implementation you are using (a config change+reboot can change that implementation, for instance).
So Java type system could be a little hampered if you extend it too much, because you need to check also runtime behavior to be sure it is correct.

Night reads

Don't Use the @Profile Annotation in a Spring Boot App! (reflectoring.io)

Testing with Spring Boot's @TestConfiguration Annotation (reflectoring.io)