Let Your Feature Flag Fly

10-09-19 Jordan Leven

Looking to gradually deploy features? Then you need to decouple deployment and code activation with feature flags.

I recently logged in to my bank to pay some bills and was greeted by a prompt. “We’re redesigning our website. Click here to experience it now!” Soon I was exploring a totally re-engineered online banking application. I found a lot of new features, like an updated site structure and upgrade to a single-page application. What might go unnoticed, though, was that the URL for the application was the same as the old one. I wasn’t on a completely different site or codebase, but I was experiencing newly activated features by opting in to preview them early. I had stumbled upon a feature flag, and the timing couldn’t have been better.

If you use Twitter, Facebook, or Netflix, chances are that you’ve encountered feature flags: small, logical operators that control which features are executed and which ones are not. At Sparkbox, our focus on continuous integration (CI) and iterative improvement led us to ask if feature flags would allow us to decouple deployment and feature activation on our own site, seesparkbox.com. We decided to test this by building a system for quietly deploying code that contained dormant features. But as an ardent feature-brancher, I was skeptical that using feature flags was a better solution than traditional feature branches.

Diving into Feature Flags

In its simplest form, a feature flag is a logical expression that determines what code gets executed at runtime. If you’re developing an update to an existing business-critical feature, feature flags are a great solution because you can both control the scale of its deployment and quickly revert functionality without a code change if you encounter an issue in production. To do this, you encapsulate the code of the new feature in a conditional that decides whether to run the old or new feature, which might look something like this:

 if ( newFeatureIsActivated() ){
    useNewFeature()
 } else {
    useOldFeature();
 }

Simple, right? The newFeatureIsActivated function is the sole decider of whether to run the new or old feature, based on which is considered active. And since the logic for determining if a feature is active lives outside of this conditional, it has the potential to be much more dynamic. This function might reference a separate controller that decides whether or not the end-user is part of our gradual rollout, allowing the feature to be activated for a small subset of the population that gradually increases until it’s available for all.

This approach was new to me. For years, I developed on feature branches. Sure, this approach has its issues (I’m looking at you, merge conflicts), but it was certainly better than working directly on master…right? I mean, if master should always be in a deployable state, what’s the alternative? So we develop on feature branches, rebase them frequently, and cross our fingers when we merge them into master.

Well, this approach has some problems. First, when you are working on a file that exists in master, there’s nothing you can do to avoid merge conflicts if other changes made to that file are then merged into master. Second, what if your merge goes okay but your feature fails in production? There’s no quick way to revert your changes that doesn’t require a code change or redeploy. Both of these issues are mitigated if we’re using feature flags.

No separate codebase, and more importantly to CI, no dedicated feature branch. The execution of features depends on whether or not a feature is activated and not just deployed. We can continually commit our small, cohesive code changes to master, and maintain a continuous feedback loop, without an unfinished feature obstructing usability while it’s being worked on. This was exactly what we were looking for when making some recent feature updates to our site.

Now You See Me, Now You Don’t

Enter seesparkbox.com. The task was simple—update the headers for our principal pages, which would require changes in both markup and CSS. But since feature flags were something we had been looking at for some time, we challenged ourselves by adding three conditions:

  • We would need the ability to deploy master without necessarily activating the updated headers.
  • Primary stakeholders would need to activate the updated headers without a code change or deployment.
  • End users would need to opt in to see the updated headers as a preview before they’re deployed to the world.

After discussing what we wanted to accomplish, we decided to build our own package that could activate and deactivate features. Although some platforms specialize in feature flags and offer software development kits (SDKs), like LaunchDarkly and Optimizely, we set off to create our own since our conditional logic was simple enough to be evaluated without a complex SDK. Based on our spec, we knew we had to create two mechanisms: one for markup and one for CSS (or, more specifically, Sass). Both forms would require only one parameter (the name of the feature flag), and our package would decide whether to include the feature or not.

Our approach to using toggles in our templates followed the convention of a simple conditional with the name of the feature. We started with the function responsible for returning our boolean.

/**
* Function used to check if the feature is active or not 
* based on the name of the feature.
*
* @param String $feature_name The name of the feature to check
* 
* @return bool  True if the feature is active
*/
function featureIsActive( string $feature_name ): bool {
  $feature_is_active;
  $feature_status = getFeatureStatusByFeatureName($feature_name);
    switch( $feature_status ) {
      case 'active':
      $feature_is_active = true;
      break;
      case 'inactive':
      default:
      $feature_is_active = false; 
      break;
    }
    return $feature_is_active;
}

Our call to featureIsActive provided the feature name, which was used to get its status from getFeatureStatusByFeatureName. After that, our function ran that status through a switch statement to interpret its status as a boolean and, poof, we had our primitive setup. We simply referenced that function in our conditional and decided which header to include. Since both our new and old headers were broken out into partials, it was just a matter of either including the updated header or the original one.

 if ( featureIsActive(‘design_refresh’) ){
   includePartial(‘page_header_updated’);
 }
 else {
    includePartial(‘page_header_original’);
 }

But according to our spec, there were multiple ways for a feature to be considered active. What about users who, like me with my banking app, wanted to opt in to an upcoming feature to experience it before it was fully activated? Well, here’s the beauty of the conditional we used: we could accommodate additional logic without altering the template code. Instead, we just added a case to our switch statement for an additional status of opt-in.

/**
* Function used to check if the feature is active or not 
* based on the name of the feature.
*
* @param String $feature_name The name of the feature to check
* 
* @return bool  True if the feature is active
*/
function featureIsActive( string $feature_name ): bool {
  $feature_is_active;
  $feature_status = getFeatureStatusByFeatureName($feature_name);
    switch( $feature_status ) {
      case ‘opt-in’:
      $feature_is_active = userHasOptedIntoFeature($feature_name);
      break;
      case 'active':
      $feature_is_active = true;
      break;
      case 'inactive':
      default:
      $feature_is_active = false; 
      break;
    }
    return $feature_is_active;
}

The parameters for featureIsActive didn’t change, but now the function could handle a new status of opt-in. In this case, we referenced a different function (userHasOptedIntoFeature) that, among other things, would check if the user had a cookie letting us know that our feature was active. But importantly, the determination of whether a feature was active or not was happening separate from the markup and could be expanded over time.

Since markup is generated server-side, we could use conditional logic to generate the markup behind the scenes based on the status of a feature. But for CSS, it’s interpreted client-side. We didn’t want to load an entirely different stylesheet based on whether or not a feature was active, so we took a different route: the updated header styles would be in the main CSS file but would be dormant unless the feature was active. You might be wondering how we attributed style changes to a specific feature in Sass. The answer is with the magic of mixins.

@mixin feature-toggle($feature_name) {
  html.feat--#{$feature_name} & {
    @content;
  }
}

So what did this do? It allowed us to encapsulate style changes inside of a mixin that was only activated if the name of the feature (prepended with “feat--”) was present in the HTML tag. The mixin was invoked below the default style attributes and simply overwrote them when the feature was determined to be activated via the presence of the class in the HTML tag (which was added by a separate controller after our package communicated what features were currently activated). This pen illustrates the relationship between the mixin, the dormant styles, and the class controller.

See the Pen SCSS Feature Toggle by Jordan Leven (@jordanleven) on CodePen.

I’m Sold! How Can I Start Using Feature Flags?

Not so fast! Yes, feature flags have a ton of really cool advantages that we’ve already outlined, but they have a few striking disadvantages.

Let’s start with a minor one: they compartmentalize code changes. We’ve all worked on a feature branch (or two) where the feature was abandoned before being merged into master. Since all code changes are isolated inside a feature branch, we can simply delete it if the feature doesn’t get promoted to master. But if we’re using feature flags, this process isn’t quite as straightforward. We need to carefully comb through our codebase and remove references to the feature before updating master, which can be a chore.

Here’s a slightly more significant disadvantage: writing unit tests. With a traditional feature branch, our updated code also contains updated unit tests for our updated features that are merged into master when we’re ready to deploy. Since these changes are not compartmentalized in the same way when using feature flags and committing to master, our unit tests now have to be written in a way that accommodates the flags and the possibility that a feature is activated or not. Again, a slight inconvenience but certainly not a deal breaker.

Moving on to the big kahuna: technical debt and bloat. Even when used correctly, feature flags contain code debt after a feature is activated for all users and considered stable. These intelligent gatekeepers that once decided which features are activated for different users become static. The gates are now open wide, never to close again, but they’re still there. A developer will have to comb through master and remove the expressions that are no longer needed. Being a chore, it has the potential to be set aside, forgotten, and before you know it, your codebase is bloated with unnecessary conditionals. Although this can be avoided by proper planning, it’s an additional step that would have to be factored into the timeline.

So Should I Use Feature Flags for My App or Website?

Much like most processes and tools in development, it depends. Feature flags check a very specific box. If that box isn’t already on your checklist, then feature flags probably don’t need to be either. Do you want to be able to roll back features without any code changes? Do you want to deploy features to a subset of a population and control their gradual deployment to the population at large? Then you need to decouple deployment and code activation—feature flags are for you. Otherwise, if you are using feature flags exclusively as a way to avoid merge conflicts or prevent feature branches from getting stale, then they might not be the best solution because of the added work that comes with them.

The time cost of using feature flags should be seen as an investment that has to be recouped with the benefits of using them. For our own website, the choice was clear: feature flags were a useful tool that allowed our developers to hand the feature-activation keys to product owners while allowing end-users the opportunity to preview upcoming changes to our site. As we continue to make our own website better through iterative improvement, we see feature flags as a worthwhile investment that compliments our process of continuous integration while allowing us to preview features of what’s to come.