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.
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.
“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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.