Polymorphic feature flags - A pattern for technical debt avoidance
May 05, 2021
While working at a previous project, we were aiming to continuously shipping to production, while following a trunk-based methodology. Feature flags are great in offerring such capabilities. We would use Optimizely to wrap-around our features and then we would ship to production, allowing our project managers to toggle features. The code would be looking something along these lines:
if(FEATURE.enabled) {
return <FeatureA/>
} else {
return <DefaultFeature/>
}
The team loved it, and hence we continued adding as many feature flags as possible, to help our project managers ship more features on-demand. However, not everything was perfect 👇
The problem
As more features got shipped to production, we suddenly had a lot of code that needed to be maintained in this branched codebase. One way to improve code maintainability would be to remove non-used feature flags, but that would require further maintainance and discipline from the team. So the question arose as to whether there is a better methodology to recycling feature flags and managing our features.
Enter polymorphic feature flags
We could utilize Polymorphism to avoid conditional code by creating abstractions (aka programming to interface) rather that writting to implementation. Here’s an example of how that might look like.
traditional approach (coding to implementation):
# traditional feature flag
if(FEATURE.enabled) {
return FeatureA()
} else {
return Default()
}
with polymorphism (coding to interface):
# polymorphic pattern
function AbsractFeature(f) {
return (enabled) => {
f(enabled);
};
}
AbsractFeature(FeatureA);
AbsractFeature(DefaultFeature);
FeatureA = enabled => { if(enabled) console.log('Feature A Implementation!')}
DefaultFeature = enabled => { if(enabled) console.log('Default Implementation!')}
Utilizing the higher-order-function methodology, we are able to transform the code in a more declarative pattern.
So what?
The code is transformed to a more declarative version, but why is that better than before? Here’s a couple of reasons why this polymorphic style is great:
- Extensibility: Easily add a new feature without adding an
if-else
statement - Traceability: features can be easily traced throughout the code with no spaghetti-like statements
- Ease of removal: At some point, feature flags will need to be removed from the codebase. The polymorphic pattern makes it extremelly easy to remove unused features
Overall this adopting polymorphism will help with code readability. It’s much easier to trace a program which uses polymorphism rather than tracking complex if-else
statements at runtime.
What about not using any line of code? Is it possible?
At Facet, we developed a facet-java-agent which does exactly that: every method and endpoint of your system contains (by default) a flag which allows toggling.
What we noticed is that while developers enjoy the flexibility of the tool and the ease of maintainance, they still prefer in-code declaration. In my experience demoing and talking to people, this is what the majority says:
“Since I am already developing a feature, adding a declaration to it is not hard and makes sense to my software lifecycle.”
The majority of the developers prefer reliability and declarability over “framework no-code-magic”. That means that while creating such an abstraction is possible, in-code declaration is still preferred over the counterpart.
Summary
This polymorhic paradigm aims to make feature declaration extremelly sructured, in order for ease of flag removal. By following this pattern, there’s an aspiration of even automatically cycling feature flags through CI. For instance, it would be quite straightforward to implement a CI task that removes inactive flags in our codebase. There are various companies working on this domain, including LaunchDarkly, GitHub and Facet.
The landscape of feature flags and continuous deployment is more vibrant than ever, and it’s exciting to what the future will look like!