Note: Part II assumes familiarity with the idea of feature flags. For the theoretical background, see Part I.
Alright, you’re convinced that feature flags are a necessary tool for your app. Let’s discuss a gem, created by the late James Golick, which make the process of feature flagging as simple as could be.
Rollout: A Feature Flagging Tool
Rollout is a super-simple gem for feature flagging. To get started,
you’ll need a redis instance accessible to your app. You likely have this in
place already. If not, you can pass in a reference to any object that accepts
set
and get
methods. But as redis is becoming a more and more common part
of the standard stack, you should probably just set up a redis instance.
Now let’s get started.
Install the gem
First, you’ll need to gem install rollout
or add gem "rollout"
to your
Gemfile and bundle install
.
Instantiate the rollout
object
Next, assuming you have a redis
variable which is an instance of
Redis:
1
|
|
and you have your rollout
object. The official docs
recommend assigning it to a global variable ($rollout
), but if you’d rather
avoid that, you can assign a rollout
variable at the global scope. (In a
Rails app, this code would belong in an initializer.)
Flipping Switches
Now you get to become a switch-flipper! Turning a feature on or off is as simple as:
1 2 3 4 5 |
|
The rollout object will always be able to tell you whether a feature is active:
1 2 3 4 |
|
Partial Activation
Sometimes you may want to activate a feature for a particular set of users. Let’s say we want to grant our premium users access to the beta version of our product.
1 2 3 4 5 6 7 8 |
|
Rollout knows that only premium_users
, as defined by the block
{ |user| user.premium? }
, have the beta
feature active, and will return true
or false based on the user passed in to #active?
.
Sometimes you may want to activate a feature for a random percentage of users. Rollout can handle that too:
1
|
|
Now 20% of users will have chat activated. Rollout also makes sure that it’s the same set of 20% of users who have chat available, so users don’t have a disjointed experience.
Inserting Branch Points
The trickiest part of using feature flags is figuring out where to branch your
code. Ideally, the code should only need one call to rollout#active?
to
invoke the correct behavior.
Controller-Level Branch Points
Sometimes, the simplest place to branch is in a controller. Let’s say we want to feature-flag an entire endpoint in a Rails app:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
If my_feature
is active, the controller does its usual work of calling models
and views. If my_feature
is deactivated, the controller responds with a
503.
This is a pretty clean example of an “On/Off” feature flag.
This pattern is very useful for experimental API endpoints, as well as debugging utilities which will often be inactive. It is also a good place to halt requests which, to succeed, will require a call to an external service that is currently experiencing downtime.
Model-Level Branch Points
Sometimes we want our application to display fundamentally different behavior depending on the situation.
In this sample scenario, we already have a GitHub API integration, and we’re trying out a BitBucket API integration as well. When we fetch the user’s repos, we want to control at runtime whether we’re just getting the GitHub repos or also checking BitBucket. Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
It might take hundreds of lines of code across multiple classes to fetch and
format those bitbucket_repos
for the User, but we found one point in our code
where we can decide whether or not all that code gets executed.
View/Decorator-Level Branch Points
Placing feature-related conditionals in our views is (almost) always a bad practice, so let’s not address it here. But we can sometimes make the case that a decorator class is the right place for a feature flag.
Let’s say that our API currently returns a top-level JSON Array, which is a Bad Practiceâ„¢. We want to follow the Good Practice and return a top-level JavaScript object. But we realize that clients may not be ready to handle that. We can set up a feature flag to switch over to the new format once our clients have had ample time to prepare:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
This is a very clear example of a This/That feature flag.
Back to Flipping Switches
We’ve spoken about how to set up switches, and how to flip them in code. How do we flip the switches when our code is running live in production?
Unfortunately, the rollout gem doesn’t provide any sort of UI, and you basically
need to SSH into your production server and modify the value in redis. In the
case of Rails, you would enter the Rails console, and execute the command
rollout.activate(:my_feature)
or rollout.deactivate(:my_feature)
.
Is there a better way?
Improvement 1: Set Up an HTTP Endpoint
If we’re running web apps, we can always add endpoints! This is how a Rails app might handle feature flipping via HTTP requests:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
You probably want to have a before_filter
to require authentication as well.
At any rate, this allows you to easily activate and deactivate features without needing to SSH anywhere.
Improvement 2: Use Hubot
Hubot comes with a simple, powerful system for making HTTP requests. Using this system, you can set up Hubot to hit these endpoints when instructed via chat command. If you have a running Hubot instance, it’s very simple to set up. This is the code to activate a feature:
1 2 3 4 5 |
|
Now, when I say hubot activate a_feature
, it takes care of it, then responds,
Activated a_feature!
Suddenly, managing feature flags becomes a breeze.
Wrapping Up
We’ve discovered how to use rollout to easily turn bits of code on and off in a live application. In Part III, we will discuss how to automatically turn off feature flags when an external service experiences downtime.
Recently I gave a talk at NYC.rb about
James Golick’s rollout
and degrade
gems. These posts are a
rehash and expansion of the material delivered there.
To learn more about James’s life and the circumstances surrounding his untimely passing, see the links below the SpeakerDeck.