What I was doing wrong — Spring autowiring and feature flagging
Series of my personal discoveries, design concerns, time waste activities, patterns inapplicabilities, and other issues I shared during the team regular retrospectives.
Spring framework allows to bootstrap working applications so fast, that any misuse or overuse of its features is leveled by the benefits of speed. How fast you can create a basic “USER CRUD REST API service” using Spring? @jekaborisov in one of Spring workshop series proved that about 2 minutes is enough. Incredible fast. We can just imagine how fast can be designed working PoC. And how excited business hearing this timing.
The problems starting when working PoC is used as the core for a long-term solution. In the period of supporting and evolving one more emotion emerging here — business is so confused, why supporting existing application may require way bigger time than initial PoC.
Autowiring
Spring provides an excellent DI framework. Component (or any other stereotype) annotation is enough to mark a specific class to be injected automatically. All you need — just autowire it in any component in any module of your application. All magic behind is for free.
Spring supports 3 types of autowiring:
- by field
- by setter
- by constructor
If there are no strict reasons to use the first 2 types — never use it. Make it a regular code review exercise to discover and fix. Your TDD practice will thank you for choosing “constructor injection”. Lombok additionally will shake your hand as well. Why?
1st reason: you can use primitive junit-mockito test type without Sring injection at all to test your feature. Constructor injection can be easily replicated with Mockito “@InjectMocks” annotation, so your test startup time will not include Spring initialization overhead. This is a hundred times faster than Spring-dependent test.
@RunWith(MockitoJUnitRunner.class)
public class SomeProviderTest {
@InjectMocks
private SomeProvider provider;
@Spy
private SomeService service;
@Test
public void testSomeMethod() {
...
}
Just compare this test class example with some common Spring test classes.
2nd reason: Lombok and “@RequiredArgsConstructor” annotation allows you to declare really elegant and simple classes, e.g.
@Component
@RequiredArgsConstructor
public class SomeProvider {
private final SomeService someService;
private final AnotherService anotherService;
}
Lombok will generate a constructor for final fields and Spring respects constructor injection because the overall class is marked as Component. Simplicity is power.
Feature flagging
Sensitive topic. Controversial topic. Many wise folks state that feature flags as a permanent option to turn on/off specific features is an anti-pattern. Allen Holub and Martin Fowler insist on the temporary use of feature flags only while the feature is in the development or adoption state.
Totally agree that the intersection of flags multiplies the complexity of supporting all combinations.
But there are business cases when the use of feature flag is justified by its benefits:
- managing independent sub-systems — e.g. disable modification API for some specific resource because of detected performance degradation
- extended security management — e.g. disable user registration module during maintenance or when security vulnerability discovered
- multi-tenant configuration — turn on/off specific feature for specific customer only, allowing it for other
- modular architecture — build and deploy specific architecture unit as a subset of modules from the core application
If you need to build your application to support some of these business criteria — good to choose modular architecture with individual configuration per module (this gives you the flexibility to manage each module individually even runtime using specific management tools like LaunchDarkly).
As a consequence, I would recommend avoiding using Spring “Component” (and other stereotypes) annotations in this architecture at all. Why? Because registering beans in global scope with using modular architecture is not good practice. With using “Component” annotation your application becomes more monolithic and the feature flag here makes small sense as turn on/off only behavior, but not managing services.
“@Configuration” class per feature allows creating a separate context of required workers for specific behavior. So toggling configuration class is an example of turning on/off everything related to this feature. As a result, the application becomes more modular and easily configurable.
@Configuration
@ConditionalOnProperty(value = "feature.enabled")
@EnableConfigurationProperties(FeatureConfig.class)
public class FeatureConfiguration {
@Value("${feature.info}")
private String info;
@Bean
public FeatureService featureService(RequiredDependency dep) {
return new FeatureService(dep);
}
@Bean
public FeatureProvider featureProvider(FeatureService service) {
return new FeatureProvider(service);
}
}
Once this feature will be activated — all required beans will be created otherwise application global context will not be polluted with standalone unused beans.
Conclusion
- To respect full power of TDD — use only constructor injection in Spring, this allows to keep test classes simple and independent from Spring context
- Use “Configuration” as a source of bean initialization — this makes the application more modular and allows to keep a single place for configuring (activating/deactivating) particular modules rather than spreading services across the whole application and manage them individually.