Chapter 9. Feature Toggle

Continuous deployment is the process of testing, integrating, and deploying software in rapid cycles in order to deliver bug fixes and new features to customers as quickly as possible. It gained popular acceptance as a cornerstone of extreme programming and agile development and is very popular among Software as a Service providers.

A feature toggle system allows you to integrate features into your codebase even before they’re finished and ready to release. During development, the features are toggled off by default. In order to turn them on, you must enable them manually. Using this method, you can deploy unfinished or untested changes into your production system without interfering with the user experience.

Feature toggles can allow software integration cycles that run in weeks, days, or even hours, as opposed to months or years. They are an essential component in a broader continuous integration system.

Feature toggles are popular in the startup community but have gained widespread acceptance in the enterprise, including larger companies such as Facebook, Google, Yahoo, and Adobe.

Organizing Features

Deciding how to organize features begins with deciding what exactly constitutes a feature. Once you figure that out, you should also decide how you’re going to classify or group features together.

Scale of a Feature

“Feature” can mean almost anything, ranging from a new page or route to an additional option on a select element. Deciding how to classify the scope of the feature is up to the project manager, but it’s easy to go overboard with features. My recommendation is to toggle at the page/route level whenever possible.

Features should be toggled on and off at the largest scale possible. For example, if you’re building a new page, you shouldn’t check whether the feature is on for every operation used to build up the page. Instead, toggle the feature at the route level. If the feature is a new element on the page, create a new view for that element, and only render that view if the feature is toggled on.

APIs should also use a feature toggle system, and the API should be capable of toggling at the route level, as well.

If you happen to use a hypermedia API and hypermedia-aware client, you can turn features on and off in the client simply by adding or removing resources from the API. For example, if you want to disable a route, just remove it from your hypermedia links, and the client won’t be able to discover and use the route.

Feature Groups

Sometimes it’s handy to group features together. You should definitely do so for features that are designed to interact with each other. For instance, you might have a user ratings feature that allows users to add stars and comments to items in a list. The stars and comments are both subfeatures of the user-ratings parent feature. If ratings are turned off, users can no longer comment or add stars to an item. It’s possible to build a feature-dependency graph to keep track of which features rely on each other to operate correctly, and then toggle them on and off simultaneously.

Groups can also be handy for marketing purposes. Sometimes it’s useful to group several exciting new features together into a newsworthy release. For products that rely on publicity cycles to spur growth, engineering must provide the technical capabilities to generate worthy news events.

It’s essential for the product team to engage the marketing team in a realistic way. The executives and marketing department should not start touting a release date until the features are in the testing stage and nearing completion. Features don’t need to be released the moment they’re complete. You can hold completed features for a scheduled marketing event and flip the toggle switch at the precise moment that marketing has promised delivery. Since the features are already deployed into production, they’ll be live and ready to use immediately.

Features are often grouped in default sets for each environment, as well. A default set commonly includes all of the features that will be toggled on in the next deploy cycle.

Lifespan of a Feature

The creation of a feature begins with naming and documentation. Before you write a line of code, name and describe the feature, along with any required criteria to qualify it for production activation. “User ratings” might be a good feature name.

Development

Start with a few unit tests. What does the feature do? How can you verify that behavior with code? For example, “A user should be able to give an item a star rating,” and, “A user should be able to comment on an item.” Once that’s done, begin the implementation. You can hide features with CSS or by wrapping the lines of code that trigger the feature in a conditional block that only executes if the feature is enabled.

Staging

At the staging phase, features can be toggled on or off. Generally, the next set of production deploy defaults will be toggled on by default in staging. Developers and QA teams can toggle on individual features to verify that they work correctly and interact well with other features.

With the user-ratings feature, testers can toggle on just the comments, just the star ratings, or both, and make sure that everything works as expected regardless of the feature configuration. One reason it’s important to lifespan feature toggles is that the various combinations of feature sets can quickly proliferate, and testing the various combinations can become unmanageable.

Some apps have feature toggle consoles that can be accessed by authorized users in staging and production. This can be a good strategy to enable manual testing. It’s also common to enable clients to toggle features on and off using query parameters in order to facilitate automated testing.

Production Testing

Using feature toggles, it’s possible to test new features in production without affecting the experience of regular users. It’s important to run another battery of tests after you have deployed features to production, and if a feature remains toggled off for very long before rollout, the smoke screen tests should be repeated again just prior to rollout.

Feature Rollout

Once features have been thoroughly tested in staging and production environments, they can be gradually rolled out to a percent of the general user population. You can start with 10% and gradually increase it. Be sure to track critical metrics during the rollout, and be prepared to roll back feature toggles that have an adverse effect on critical metrics.

It’s possible that simple star ratings could improve metrics, but comments could add clutter and distraction. Rolling features out gradually allows you to assess the impact on the overall user experience without taking a big loss if something doesn’t work out as expected.

Default Activation

Default activation means that the feature is active by default across the whole system, and not on a user-by-user basis. A feature can stay in this state until it is no longer needed or until it is phased out, or it can progress to full integration status.

After seeing the results from the percentage rollout, you might want to make star ratings default while you try to refine the user experience for comments.

Full Integration

Once a feature has made it this far, it’s time to remove the feature toggle from the source code and the feature name from the features database. Cleaning up feature branching will reduce code complexity and make it easier to maintain the code going forward.

At this point, you would remove the conditional code around both the user ratings top-level feature and the star ratings. You’ll still have feature toggle code for comments until you find a way to integrate them without hurting the user experience.

Implementation

On the client side, you can show and hide features based on the presence of CSS classes. Take the following HTML:

  <!DOCTYPE html>
  <html>
    <head>
      <title>Feature Toggle Demo</title>
      <style>
        li {
          display: inline-block;
          margin-left: 1em;
        }

        .new-feature {
          display: none;
        }

        .ft-new-feature .new-feature {
          display: inline-block;
        }
      </style>
    </head>
    <body>
      <p>Add <code>?ft=new-feature</code> to the end
        of the url to see the new feature.</p>
      <div class="menu">
        <ul>
          <li class="old-feature">Boring old feature</li>
          <li class="new-feature">New feature!</li>
        </ul>
      </div>
      <script src="../dist/feature-toggle-client.js">
      </script>
      <script>
        // Activate the feature toggle system.
        var ft = featureToggle();
      </script>
    </body>
  </html>

By setting the .ft-new-feature class on the body tag, you can make the feature show up in the menu. Here’s a client-side script that can manage feature toggles in the browser:

  'use strict';

  var union = require('mout/array/union'),
    contains = require('mout/array/contains'),
    EventEmitter = require('events').EventEmitter,
    stampit = require('stampit'),

    /**
     * Grab the url parameters and build a map of
     * parameter names and values.
     * @return {Object} params object
     */
    getParams = function getParams() {
      var params = {};
      if (location.search) {
        var parts = location.search.slice(1).split('&');

        parts.forEach(function (part) {
          var pair = part.split('=');
          pair[0] = decodeURIComponent(pair[0]);
          pair[1] = decodeURIComponent(pair[1]);
          params[pair[0]] = (pair[1] !== 'undefined') ?
            pair[1] : true;
        });
      }
      return params;
    },

    /**
     * Get a list of feature names from the url
     * parameters.
     * @return {Array} Features list
     */
    getParamFeatures = function getParamFeatures() {
      var features = getParams().ft;
      return features ? features.split(',') : undefined;
    },

    /**
     * Combine the list of base features with
     * the features passed in via URL parameters.
     * @type {Array} active features
     */
    getActiveFeatures =
        function getActiveFeatures(baseFeatures,
          paramFeatures) {
      return union(baseFeatures, paramFeatures);
    },

    /**
     * Takes an array of features and creates a class for
     * each of them on the body tag.
     * New features should be hidden in CSS by default
     * and revealed only when the feature toggle is set:
     *
     * .new-feature { display: none; }
     * .ft-new-feature .new-feature { display: block; }
     * 
     * @param {Array} features An array of active features.
     */
    setFlags = function setFlags(features) {
      var featureClasses = features.map(function (feature) {
          return 'ft-' + feature;
        }).join(' '),
        classNames = document.getElementsByTagName('body')[0]
          .className.split(' ').filter(function (className) {
            return !className.match(/^ft/);
          });
      document.getElementsByTagName('body')[0].className = 
        classNames.join(' ') + ' ' + featureClasses;
    },

    /**
     * Take an optional list of features, set the feature
     * classes on the body tag, and return the feature
     * toggle object.
     * @param {Array} baseFeatures List of base features.
     * @return {Object} feature object
     */
    setFeatures = function setFeatures(baseFeatures) {
      var paramFeatures = getParamFeatures(),
        activeFeatures = getActiveFeatures(baseFeatures,
          paramFeatures),

        methods = {
          /**
           * Check to see if a feature is active.
           * @param  {String} feature 
           * @return {Boolean}
           */
          active: function active(feature) {
            var testFeature = feature && feature.trim &&
              feature.trim();
            return contains(activeFeatures, testFeature);
          },

          /**
           * Activate a list of features.
           * @emits activated
           * @param  {Array} features 
           * @return {Object} this (for chaining)
           */
          /**
           * activated event.
           *
           * @event activated
           * @type {Array} activated features
           */
          activate: function activate(features) {
            activeFeatures = union(activeFeatures, features);
            setFlags(activeFeatures);
            this.emit('activated', features);
            return this;
          },

          /**
           * Deactivate a list of features.
           * @emits deactivated
           * @param  {Array} features 
           * @return {Object} this (for chaining)
           */
          /**
           * deactivated event.
           *
           * @event deactivated
           * @type {Array} deactivated features
           */        
          deactivate: function deactivate(features) {
            activeFeatures = 
              activeFeatures.filter(function (feature) {
                return !contains(features, feature);
              });
            setFlags(activeFeatures);
            this.emit('deactivated', features);
            return this;
          }
        },

        // Creates the feature toggle object by
        // composing these methods with an
        // event emitter using the Stampit
        // prototypal inheritance library.
        ft = stampit.compose(
          stampit.convertConstructor(EventEmitter),
          stampit(methods)
        ).create();

      // Kick things off by setting feature classes
      // for the currently active features.
      setFlags(activeFeatures);

      return ft;
    };

  module.exports = setFeatures;

On the server side, you’ll want to check the URL parameters and a features cookie (a saved list of feature overrides), get the list of currently active features from the feature database, and combine that data to calculate the currently active features for the current user.

The list of features sent by the server is often influenced by a number of different criteria. A common example: say you want to roll out a feature to 20% of the users. You can use a clever trick to base the user percentile on the user ID. Of course this won’t produce exactly even distributions, but for user communities numbering in the thousands, it’s usually a close enough approximation:

  userPercentage = function userPercentage(userId, percent) {
    var id = parseInt(userId, 36);
    return (id % 100 < percent);
  };

Other common criteria include whitelisted users and whitelisted user groups, user sign-up date, paid versus unpaid users, andtype of account.

Conclusion

Feature toggles offer tremendous gains in the integration process, allowing companies to ship updates without the nightmare of coordinating a release across many features and teams working on new product features.

They also allow you to test new features in the actual production environment without interfering with the user experience.

Gradual feature rollout allows you to gauge the impact of a change and roll back changes with a minimal impact on user experience and productivity.

As you’ve seen, it’s not hard to integrate feature toggles into your code, and it offers a great deal of value. It’s no wonder that feature toggles have been adopted by so many industry-leading software teams.

Get Programming JavaScript Applications now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.