Skip to content

Instantly share code, notes, and snippets.

@beldar
Created November 20, 2017 15:50
Show Gist options
  • Save beldar/8a1fab6db89a7e16797a73304c6f2aca to your computer and use it in GitHub Desktop.
Save beldar/8a1fab6db89a7e16797a73304c6f2aca to your computer and use it in GitHub Desktop.
Implementation proposal for feature toggles and multi-variant toggles with React

Toggles

Types of toggles

Release Toggles

These toggles are then corner stone of the Continuous Delivery cycle, they are short lived and mostly for development purposes.

The idea is to wrap all development of the next release in to a toggle (i.e. v2.0.2), this way development can carry on while been able to still deploy to production using the same branch.

Once the release is live and has been integrated on production, all the conditionals and the whole of the release toggle is removed and all functionality integrated as part of the normal codebase, then the toggle for the next release is created.

Its ok to use lots of ifs on the implementation of these since they will be removed on the short term.

Experiment Toggles

Also known as multivariant or A/B Testing, these are created to experiment with different variations of a UI element, these are generally mid to short lived and they are for marketing or UX purposes.

These toggles will always be implemented with a default or the existing behaviour as a default, and then other variations will appear instead if the toggle is active, the best way to implement these is using whenActive as we'll see later on.

When a decision has been made on which version performs best the toggle will be removed along with any discarded variations.

Feature Toggles

This toggles are used when the business or operations wants a certain feature to be able to be deactivated or activated at any time.

This could include something like the ability to deactivate Nectar services or certain pages from appearing, or also something like the way the Node layer behaves on production.

These toggles can be long lived and used for business purposes as well as operational purposes.

Since these toggles can be on the codebase for a long time, the implementation should be more careful and future proof, ideally applying ideas of inversion of control or factories to avoid writing lots of conditionals everywhere.

Implementation

The implementation on the frontend will be quite straight forward.

The toggle configuration is loaded on the ToggleStore on initialisation and could (albeit not implemented) be changed on runtime.

The toggles live in an Immutable.Map with the toggle name as the key and a boolean as the value, true for active toggles, false for inactive.

There are two utilities created to make the implementation quite easy, the first one is the withToggles util which is meant for components.

Important: To make easier the later removal of toggles, we're going to add a comment above the line every time we use a toggle, for example // TOGGLE: toggle-name. That will make it easier to search for all checks of that toggle and remove them quicker.

withToggles.js

It wraps a given component into another component which loads and listens for changes on the ToggleStore and adds two methods on the props of the child component, isActive and whenActive. They're used like this:

const withToggles = require( '../utils/withToggles' );

const MyComponent = React.createClass({

  ...

  someMethod() {
    // TOGGLE: some-toggle
    if ( this.props.isActive( 'some-toggle' ) ) {
      return somethingElse;
    }

    return something;
  }

  ...

  render() {
    // TOGGLE: variation-b
    return this.props.whenActive( 'variation-b', MyComponentB, MyComponentA );
  }
});

module.exports = withToggles( MyComponent );

The isActive method accepts a toggle name, and returns a boolean.

The whenActive method accepts a toggle name as the first argument, the second argument is whatever you want to return if the toggle is active, and the third argument is whatever you want to return if the toggle is not active. If the third argument is not passed it returns null if the toggle is not active.

This implementation also makes it really easy to test, since you can just pass your toggle preferences by props to the component you're testing as we'll see later on.

toggles.js

The second utility is meant to be used anywhere else other than a component, for example on services, stores, etc.

It also feeds from the ToggleStore (but without subscribing) and it exposes the same two methods, isActive and whenActive with the same functionality as the withToggles methods.

Some examples of use:

const { isActive, whenActive } = require( '../util/toggles' );

...
// TOGGLE: my-toggle
if ( isActive( 'my-toggle' ) ) {
  //do something
}

...
// TOGGLE: my-other-toggle
return whenActive( 'my-other-toggle', this.someMethod, this.someOtherMethod );

...
// TOGGLE: some-toggle
let myObject = whenActive( 'some-toggle', objectB, object );

...
// TOGGLE: a-toggle
let response = whenActive( 'a-toggle', purefunctionA, purefunctionB );

...
// TOGGLE: string-toggle
let title = whenActive( 'string-toggle' , 'Title B', 'Title A' );

Testing

Testing components with different toggle configuration is pretty easy.

The first thing to do for consistency and later integration is to wrap your toggle tests on a different describe suite.

Then you can pass a mocked function of whichever utility function you use on the component through props:

//This component uses isActive
let active = () => true;
let inactive = () => false;

describe( 'My component' () => {
  it ( 'should render as always', () => {
    let component = mount( <MyComponent isActive={ inactive } /> );
  });
  ...

  describe( 'TOGGLE: my-toggle', () => {
    it ( 'should show something different when my-toggle is active', () => {
      let component = mount( <MyComponent isActive={ active } /> );
      ...
    });
  });
});

Testing other code is also similarly easy, there is a mocked version of toggles.js already in place that you can use like this:

const toggles = require( '../util/toggles' );

toggles._setToggles({
  'some-toggle': false
});

describe( 'MyService', () => {
  it ( 'should work as usual', () => {
    ...
  });

  describe( 'TOGGLE: service-toggle', () => {
    beforeEach( () => {
      toggles._setToggles({
        'some-toggle': true
      });
    });

    it ( 'should do something special now', () => {
      ...
    });
  });
});

Other considerations

There are some ways to avoid writing too many conditionals when writing a new functionality, here are some rules of thumb to consider.

As a general rule, if you can extract the needed new functionality into a new method that doesn't affect the rest of the code and avoid conditionals, do it that way.

Components

  • If the new feature changes the fundamental way a component works or the refactor makes it extremely difficult to make it backwards compatible, write a new component with a different variation name and use whenActive to load it conditionally on the parent. When/if the feature is released you can get rid of the old component all together.

  • If the new feature requires a brand new component with no backup, use whenActive without third argument to render it: { this.props.whenActive( 'toggle', <BrandNewComponent /> ) }.

Services and Stores

  • If its a new service/store, implement it directly on a new file.

  • If its an existing service/store which adds new functionality, add new methods and use them only on the pertinent components, it shouldn't need any conditionals.

  • If its an existing service/store which needs a refactor on an existing functionality but can still be backwards compatible, use toggle conditionals.

  • If its an existing service/store which needs a refactor that makes it break backwards compatibility, duplicate the file into a new file where you can do the refactor, then use it conditionally where needed. When the feature is release you can get rid of the old code.

Controlling the Toggles

This is an area to explore still. Right now the toggles are controlled by a simple json file on server/toggles/index.js, they are loaded on start up time and stay like that until changed and reloaded.

Ideally we would want more flexibility around that.

The easiest, fastest way to control the toggles with our current architecture would be to use Consul's key-value storage to control the values of the toggles on each environment without requiring a code change. The config could be loaded at start-up time, or in a time interval (i.e. each hour).

More advanced and bespoke dashboards exist to control feature toggles, for example The Finantial Times has open sourced a hosted API and dashboard solution called next-flags-api.

There's also Flip which also allows you to use cookies or DB to enable toggles per user or general.

Other options include the use of SaaS solutions such as LaunchDarkly or Optimizely (this last one mainly only for A/B testing).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment