Feature

Toggle

Driven

Development

Igal Steklov

CEO & FED

Cheers

🍻

Good Morning

Deploy to Production?

How Often Do You

How stable is it?

TDD

Test Driven Development

CI/CD

Continuous Integration & Deployment

How do you control when code is exposed to users?

Feature Toggles

you need...

Feature Toggles?

What are

Feature Toggles

(or Feature Flags)

import React, { Component } from 'react';
import featureToggles from 'services/featureToggles';

class ExampleComp extends Component {
    render () {
        return featureToggles.isEnabled('my_new_feature')
            ? <a href="...">Click me for new feature</a>
            : <p>New feature is coming soon</p>
    }
}
services/featureToggles.js
import bulletTrain from 'bullet-train-client';
featureToggles.isEnabled('my_feature') // true | false
import bulletTrain from 'bullet-train-client';

const config = {
  environmentID: process.env.BULLET_TRAIN_ENV_ID
};

class FeatureToggles {
  // Called once app loaded and user data retrieved
  init(user) {
    bulletTrain.init(config);
    bulletTrain.identify(`${user.id}_${user.fullName}`);
  }

  // Called on logout from our app
  destroy() {
    bulletTrain.logout();
  }

  isEnabled(featureToggle) {
    return bulletTrain.hasFeature(featureToggle);
  }
}

export default new FeatureToggles();
[
    {
      "id":3000,
      "feature":{
         "id":818,
         "name":"notes_list",
         "created_date":"2019-05-25T15:22:26.058231Z",
         "initial_value":null,
         "description":null,
         "default_enabled":true,
         "type":"FLAG",
         "project":325
      },
      "feature_state_value":null,
      "enabled":true,
      "environment":814,
      "identity":null
    },
    { ... },
    { ... } 
]
~ curl 'https://api.bullet-train.io/api/v1/flags/' -H 'X-Environment-Key: BgwB3E6'

It's just a simple JSON

<body data-ft-new_design>
  ...
</body>
.button {
    font-size: 12px;
}

body[data-ft-new_design] .button {
    font-size: 18px;
}

Also in your CSS

.button {
    font-size: 12px;

    @include when-feature-enabled("new_design") {
    // Compiles to body[data-ft-new_design] .button
	font-size: 18px;
    }
}

Or SASS...

New Dev
Workflow

Driven by Feature Toggles

1.

Start with creating a FEATURE TOGGLE

2.

Block entry points with conditions

import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import featureToggles from 'services/featureToggles';

class Navigation extends Component {
    render () {
        return (
          <nav>
              <ul>
                <li><NavLink to="/">Homepage</NavLink></li>
                <li><NavLink to="/reports">Reports</NavLink></li>
                {featureToggles.isEnabled('notes_list') &&
                    <li>
                        <NavLink to="/notes">
                            Notes
                        </NavLink>
                    </li>
                }
              </ul>
          </nav>
        );
    }
}
import React, { Component } from 'react';
import history from 'utils/history';
import featureToggles from 'services/featureToggles';

class NotesList extends Component {

    //UNSAFE_componentWillMount
    constructor(props) {
        super(props);

        if (!featureToggles.isEnabled('notes_list')) {
            history.push('/error-page/404');
            return;
        }
    }

    render () {
        return null;
    }
}

3.

Work on your feature,
deploy to Production

import React, { Component } from 'react';
import history from 'utils/history';
import featureToggles from 'services/featureToggles';

class NotesList extends Component {

    //UNSAFE_componentWillMount
    constructor(props) {
        super(props);

        if (!featureToggles.isEnabled('notes_list')) {
            history.push('/error-page/404');
            return;
        }
    }

    render () {
        return (
            <div>
                Notes list component is under development,
                so no user reach current component, yay 🎉.
            </div>
        );
    }
}

4.

Share work progress with PMs & QA

5.

Controlled Release of Features,
Bug Fixes
or Infra Refactoring

AB Test or just Release

6.

Monitor Production: Biz & Tech KPIs

It's OK to...

What's so Great About FTs? 

  1. Better collaboration with PMs, QA and other stakeholders.
     
  2. Ability to gradually rollout sensitive changes.
     
  3. Quickly resolve Production issues by closing FT (aka Kill Switch).
     
  4. Verify code works on Production, daily.
     
  5. No need for staging if you have tests – auto-deploy to Prod when CI passes.
     
  6. Better code – everything is "Production Code"

Tips

1.

Feature Toggle
Any Non-trivial
Code Change

  • Tiny feature? YES!
  • Bug fix? YES!
  • Internal infra change? YES!

2.

It's OK to FT
Entire Components

import React, { Component, Fragment } from 'react';
import { Switch, Route } from 'react-router-dom';
import featureToggles from 'services/featureToggles';

import DashboardPage from 'containers/DashboardPage';
import ListPage from 'containers/ListPage';
import ListPageV2 from 'containers/ListPageV2';

export default class App extends Component {
  render() {
    return (
      <main>
        <Switch>
          <Route path="/" component={DashboardPage}/>
          <Route path="/list" render={() => featureToggles.isEnabled('list_page_v2')
            ? <ListPageV2/>
            : <ListPage/>
          }/>
        </Switch>
      </main>
    );
  }
}

3.

Remember the Backend APIs

import axios from 'axios';
import featureToggles from 'services/featureToggles';

export function listNotes() {
  const apiVersion = featureToggles.isEnabled('notes_api_v2')
    ? 'v2'
    : 'v1';

  return axios.get(`api/${apiVersion}/notes/`);
}

4.

Plan Feature Toggle so that they can be turned off if needed

class NotesList extends Component {
  render () {
    const { notes = [] } = this.props;

    if (featureToggles.isEnabled('notes_list')) {
      return (
        <ul>
          {notes.map(note => 
            <li key={note.id}>{note.text}</li>
          )}
        </ul>
      );
    }

    return (<p>Notes are coming soon</p>);
  }
}
class NotesList extends Component {
  render () {
    const { notes = [] } = this.props;
    const isNotesExist = notes.length > 0;

    if (isNotesExist || featureToggles.isEnabled('notes_list')) {
      return (
        <ul>
          {notes.map(note => 
            <li key={note.id}>{note.text}</li>
          )}
        </ul>
      );
    }

    return (<p>Notes are coming soon</p>);
  }
}

5.

Cleanup your Feature Toggles after release

class NotesList extends Component {
  render () {
    const { notes = [] } = this.props;
    const isNotesExist = notes.length > 0;

    if (isNotesExist || featureToggles.isEnabled('notes_list')) {
      return (
        <ul>
          {notes.map(note => 
            <li key={note.id}>{note.text}</li>
          )}
        </ul>
      );
    }

    return (<p>Notes are coming soon</p>);
  }
}
class NotesList extends Component {
  render () {
    const { notes = [] } = this.props;

    return (
      <ul>
        {notes.map(note => 
          <li key={note.id}>{note.text}</li>
        )}
      </ul>
    );
  }
}

Step
1

Step
2

Step
3

  1. Easy to Start

  2. Safer Deploys

  3. Stable Production

  4. PMs & QA ❤️ You

  5. Infra for A/B Tests

Thank You :)

Igal Steklov

igal@webiya.co.il

+972-54-6490123

Slides at: tiny.cc/FTDD

Questions?

Made with Slides.com