Toggle - Feature toggles (a.k.a. flippers, flags, switches etc.)
use Redis;
use Toggle;
# This probably happens once for your application, e.g. via Bread::Board
my $redis = Redis->new;
my $toggle = Toggle->new( storage => $redis );
# Define any groups you might need - your sub will be passed the $user
# object that you send to is_active(), and should return true/false.
$toggle->define_group( staff => sub {
my $user = shift;
return $user->has_role('staff');
});
...
# In your controllers
# $user must either have an ->id accessor, or return the id when used
# as a string.
if ( $toggle->is_active(chat => $user) ) {
# Show this user the shiny new chat feature
}
...
# Elsewhere, in a management UI tool
$toggle->activate_user( chat => $bob ); # Turn it on for Bob
$toggle->activate_group( chat => 'staff' ); # Enable staff testing
$toggle->activate_percentage( chat => 10 ); # 10% rollout
...
if ( my $variant = $toggle->variant( chat => $user ) ) {
if ( $variant eq 'a' ) {
...
}
elsif ( $variant eq 'b' ) {
...
}
}
Toggle lets you activate features for different users, storing this configuration in a database like Redis. You can use it to roll out your code to a percentage of users, or specific users/groups that you define.
Constructor.
my $toggle = Toggle->new( storage => $store );
The storage attribute must be an object that supports get($key)
, set($key, $value)
and del($key)
methods. This happens to be the same interface that Redis uses, but you could make your own.
Check whether a feature is active. This is the method which you would expect to use all the time in your application code. Can be called in two ways - either to check whether a feature is active for everyone:
$toggle->is_active( 'chat' );
or to check whether it is enabled for a particular user:
$toggle->is_active( chat => $user );
In the second case, the $user object must have an id accessor, or stringify to the id (so a scalar is fine).
Enable a feature for everyone.
$toggle->activate( 'chat' );
Disable a feature for everyone. Useful if you discover a critical bug and want to turn it off altogether. Overrides all the user/group/percentage activations that may have been on previously.
$toggle->deactivate( 'chat' );
You may wish to use this method to define groups for use below in "activate_group". You pass a subroutine which will accept a single argument (the $user object given to "is_active") and will return a boolean to say whether the user is in the group.
$toggle->define_group( admins => sub { shift->is_an_admin() } );
The group 'all' is defined by default.
$toggle->define_group( all => sub { 1 } );
Turn on a feature for a group. The group must have been defined previously (see "define_group"), or be the "all" group.
$toggle->activate_group( chat => 'admins' );
Turn off a feature for a group. The group must have been defined previously using "define_group".
$toggle->deactivate_group( chat => 'admins' );
Note that any users which have been explicitly enabled via "activate_user" will still be able to see the feature, even if they are in the group.
Turn on a feature for an individual user.
$toggle->activate_user( chat => $user );
Turn off a feature for an individual user.
$toggle->deactivate_user( chat => $user );
Activate a feature for a percentage of users, such as for an incremental rollout of a new feature.
$toggle->activate_percentage( chat => 10 );
The algorithm used is CRC32( $user->id ) % 100 < percentage
, so as the percentage increases, users do not fall out of the group.
Due to this implementation, it is important to understand that for the same set of users across multiple experiments, those who might fall within the 10th percentile for one rollout would be the same set of users to fall within the 10th percentile of the rollout for an entirely different experiment.
This also has implications for using a subset of users admitted by one toggle for use in a subsequent toggle check, where both checks are based off percentage. For example, admitting 50% of users for a feature and then attempting to derive 50% from that subset using another toggle. Instead of the 2nd toggle rolling out a feature to 25% of the users it checks against, it will be rolling it out to 100% of those users.
Finally, note that activating the feature for 100% of users will activate the feature globally (i.e. when "is_active" is called without a $user argument).
Deactivate a feature for all users that were receiving an incremental rollout.
$toggle->deactivate_percentage( 'chat' );
Note that users who were previously activated individually or as part of a group will still have the feature active.
List all the features that the storage knows about.
my @features = $toggle->features();
Add a feature to the storage, but do not activate it for anyone.
$toggle->add_feature( 'chat' );
Remove a feature from the storage.
$toggle->remove_feature( 'chat' );
This will disable it for all users, and will also stop it appearing in the output of "features".
Test what variant that user should see.
$toggle->variant( chat => $user );
Set the variants for a given feature.
$toggle->set_variants(
chat => [
a => 20,
b => 40,
],
);
Github repo: https://github.com/cv-library/Toggle
Inspired/shamelessly ported from https://github.com/FetLife/rollout
Copyright © 2014 CV-Library Ltd.
Licensed under the same terms as Perl 5.