I Wrote My Own Feature Flag System
posted under category: General on December 29, 2022 by Nathan
A blog series in which I confess to accidentally having written my own poor version of a solved problem
Feature flags really are a solved problem, right? I mean, there are big cloud services that will manage them for you. There are frameworks in every language, both as standalone tools, and full-fledged systems. We have feature flags in our build pipelines. Feature flags in our cloud services. I mean, this problem really seems like it's solved.
Then again, a lot of those systems, tools, libraries, and services seem really overkill. Some of them are massive. Even the smaller ones expect me to be A/B testing constantly. Maybe I'm not the FAANG company that this software is looking for. All of them seem to want to manage my users somehow - which I get, that's how you test with A/B groups.
What did I want though? I want the ability to deploy, even to production, without changing the user experience. I'm working with a lot of younger, and offshore developers, and I want to be able to turn off a feature if it doesn't pan out. My product manager wants to be able to specify a couple users for a specific beta test, then eventually roll the change out to everyone. These are not complicated use cases.
Unlike a lot of these stories where I confess to writing my own frameworks because I didn't know better, this one is really because I didn't like any of the frameworks out there. Plus how hard could it be? Turns out, not very!
The very first feature that we wanted to put a flag around was something that just wasn't code-complete yet. Using Vue.js as the template engine, my "feature flag" looked like this:
<new-feature-page v-if="false" />
That's terribly simple, right? When the feature was done, we just have to take off the v-if
. Don't worry. We're Agile. This first version is the skateboard, or whatever analogy you like to use that explains how this is the simplest possible version that we can ship to our users. We can ship it, unit test it, but not show it to a single user.
In version two, we still didn't have any real data, since the database team was still working on the initial build scripts. The design of this FF system is to run it from the database and populate flags in our UI dynamically. Until that's a reality, I made a simple object with some of our known flag names, then a convenience function. That grew into a Vue plugin, something like this:
export const isFeatureEnabled = (featureName) => allFeatures.includes(featureName);
Vue.prototype.$feature = isFeatureEnabled;
So around the application, a .js
file may import isFeatureEnabled
or anywhere in a Vue component, my $feature
plugin is ready:
<template>
<!-- Used directly in the template -->
<new-feature-page v-if="$feature('my-new-feature')" />
</template>
<script>
// or used in the script area
export default {
mounted: {
if (this.$feature("my-new-feature")) {
// do stuff
}
}
};
</script>
That's about as convenient as it can be.
This second version used a static list list of feature flag names, added to a .js file in the UI application. It's nice because it's as fast as RAM, but it was not very dynamic yet.
Eventually the database was fleshed out and we created a FeatureFlag
table. When a user logs in, they receive a packet of data - things like their name, their permissions, and some contextual information like what kind of airplanes they work on. I added the active feature flags to this login data packet through a very simple query like:
select FeatureName
from FeatureFlag
where Enabled = 1
So there it is, mission accomplished, right? That essentially handles it for the end-users. We have just a couple gaps left.
The next thing to add was tooling in our server-side application, like we did for our IU. Even through today, we haven't done much here still, but there's an injectable FeatureFlagService
that can query the data, and maybe someday we will cache those results. We actually don't use this a lot, so the server-side evolution has slowed to a crawl.
Finally, for my SQL Server database, I added a simple SQL function that checks the status of a flag and returns 1 or 0. It's easy and is used heavily, mostly to switch entire procedures based on the currently active flag. This approach leads to versioned duplication of code, so we try to keep it manageable with a good naming scheme.
Feature flag taxonomy is a tough challenge. We want the name of the feature to represent where it is, and what it does, and it needs to fall in line with other flag names so that when you look at a sorted list of them, you know what they all do. We settled on a few naming rules. Our feature flags are kebab-case (that is, lowercase with hyphens instead of spaces) and hierarchically separated with slashes, named after parts of the application. For example, "login/include-xyz-data" or "admin/feature-flag-tool".
The last piece of the puzzle was a GUI to manage the flags. Another simple query to get the list, some easy APIs to switch the flags on and off. Easy as cake.
What went wrong?
As usual, building my own frameworks has it's complications and downsides. For instance, how do features migrate between environments? We have a system that involves bundling flags with SQL deployments, so anytime a database change goes, so do the new flags. But we don't have a central location to view and manage them across all the different testing and production environments.
One point of confusion we had, was that my international team didn't see our vision for targeting flags at particular users or groups, so we had the opportunity to build those parts twice. The end result, today, is called Feature Audience Groups where we add users to a group, then enable individual flags for that group. It's a simple additive-only methodology. I developed the concept of "feature flags move forward" - which means that flags don't usually roll back; a successful flag starts in the off position, then turns on for a group, then on for everyone, then we remove the flag. This forward motion simplifies the audience group flag management because flag groups cannot turn off a flag, they can only turn them on.
What did I learn?
A feature flag system really is just as simple as an if
. I would encourage absolutely everyone to build one for themselves.
Today, we can safely deploy broken and half-finished features, and beta test them right in prod. We don't have to wait until everything is 100%. This freedom is worth the effort!