FEATURE FLAGS ARE
FLAWED
LET'S MAKE THEM BETTER
Created by Stephen Young / @young_steveo
FEATURE FLAG?
I've seen a lot of names for this concept tossed
around: feature bits, ags, ippers, switches
and the like. There seems no generally
accepted name yet.
— Martin Fowler
SIMPLE TOGGLE
class AmazingRecipe
{
public function getSauce()
{
$useNewRecipe = false;
// $useNewRecipe = true;
if ($useNewRecipe) {
// new formula here
} else {
// original code here
}
}
}
TRADITIONAL
FEATURE FLAGS ARE…
FLAWED
...ACTUALLY PRETTY USEFUL AND NOT REALLY FLAWED PER SE, BUT THEY COME WITH A FEW
CHALLENGES AND I THINK WE CAN IMPROVE THE MODEL, SO
LET'S MAKE THEM BETTER
SIMPLE TOGGLE
class AmazingRecipe
{
public function getSauce()
{
$useNewRecipe = false;
// $useNewRecipe = true;
if ($useNewRecipe) {
// new formula here
} else {
// original code here
}
}
}
❌
❌
Not Testable
SIMPLE TOGGLE V2
class AmazingRecipe
{
public function getSauce()
{
if ($this->useNewRecipe()) {
// new formula here
} else {
// original code here
}
}
public function useNewRecipe()
{
return false; // true
}
}
←
←
Can Be Stubbed
MOCK TOGGLE
/**
* @dataProvider newFormulaProvider
*/
public function testGetSauce($useNew, $sauce)
{
$sut = $this->getMock('AmazingRecipe', ['useNewRecipe']);
$sut->expects($this->once())
->method('useNewRecipe')
->will($this->returnValue($useNew));
$this->assertEquals($sauce, $sut->getSauce());
}
public function newFormulaProvider()
{
return [
[ false, 'old Recipe' ],
[ true, 'new Recipe' ]
];
}
SIMPLE TOGGLE V2
class AmazingRecipe
{
public function getSauce()
{
if ($this->useNewRecipe()) {
// new formula here
} else {
// original code here
}
}
public function useNewRecipe()
{
return false; // true
}
}
❌❌❌
Not Maintainable
Not Con gurable
Not My Concern
FEATURE ROUTER
class AmazingRecipe
{
public function getSauce()
{
if ($this->useNewRecipe()) {
// new formula here
} else {
// original code here
}
}
public function useNewRecipe()
{
return Flags::enabled('AmazingRecipie.NewSauceFormula');
}
}
FEATURE ROUTER
final class Flags
{
protected static $map = [];
public static function enabled($key)
{
if (empty(static::$map)) {
// hydrate map
}
return !empty(static::$map[$key]);
}
}
TRADITIONAL FEATURE FLAG SYSTEM
Curb Long-Lived Feature Branches
Easy To Use (If This Then That)
Testable
TONS OF APPLICATIONS
Release Toggles - change frequently, short lived
Timed Releases
Operations Toggles - sometimes long lived, rarely change
WHAT'S NOT TO LIKE?
CYCLOMATIC COMPLEXITY
public function getSauce()
{
if ($this->useNewRecipe()) {
if ($this->testSpicyVersion()) {
// add spice
}
if ($this->fixEnabled()) {
// fix bug in new code
}
} else {
// original code here
if ($this->fixEnabled()) {
// fix bug in old code
}
}
}
Complexity: 5
ALL OR NOTHING
CANARY DEPLOYMENTS
COHORTS
[…] a cohort is a group of subjects who have
shared a particular event together during a
particular time span.
— Wikipedia
MORE CONDITIONS?
if (
Flags::enabled('SomeFeature') &&
$user->canSeeFeature('SomeFeature')
) {
// execute feature code
}
class User
{
public function canSeeFeature($feature)
{
// check the db or user session?
}
}
INTRODUCING SWIVEL
Swivel can enable features for a subset of users.
No more complex control ow; Swivel takes care of
determining code paths.
BASIC CONCEPTS
1. Segment users into 10 cohorts. Swivel calls these Buckets.
2. Associate a Feature to a number of active Buckets.
3. Write code that runs when a particular Feature is enabled.
Swivel calls these Behaviors.
SEGMENT USERS INTO BUCKETS
id user_id bucket_id
1 160 6
2 956 2
3 189 7
4 412 2
ASSOCIATE FEATURES TO BUCKETS
id slug buckets
1 "AwesomeSauce" "[ ]"
2 "AwesomeSauce.Spicy" "[1,2]"
3 "AwesomeSauce.Saucy" "[3,4]"
BOOTSTRAP
$bucket = 5; // From Session or DB
$map = [
'AwesomeSauce' => [1,2,3,4],
'AwesomeSauce.Spicy' => [1,2],
'AwesomeSauce.Saucy' => [3,4]
];
$config = new ZumbaSwivelConfig($map, $bucket);
$swivel = new ZumbaSwivelManager($config);
TOGGLE EXAMPLE
class AmazingRecipe
{
public function __construct(ZumbaSwivelManager $swivel)
{
$this->swivel = $swivel;
}
public function getSauce()
{
return $this->swivel->forFeature('AwesomeSauce')
->addBehavior('Spicy', [$this, 'getSpicyFormula'])
->addBehavior('Saucy', [$this, 'getSaucyFormula'])
->defaultBehavior([$this, 'getFormula'])
->execute();
}
protected function getSpicyFormula() { }
protected function getSaucyFormula() { }
protected function getFormula() { }
}
SHORTHAND EXAMPLE
class AmazingRecipe
{
public function __construct(ZumbaSwivelManager $swivel)
{
$this->swivel = $swivel;
}
public function getSauce()
{
return $this->swivel->invoke(
'AwesomeSauce.New',
[$this, 'getNewSauce'],
[$this, 'getOldSauce']
);
}
}
METRICS FOR CANARY DEPLOYMENTS
SWIVEL LOGGING
PSR-3 LOGGER AWARE
$config = new ZumbaSwivelConfig($map, $bucket, $psr3Logger);
// or
$config->setLogger($psr3Logger);
SWIVEL METRICS
STATSD STYLE METRICS INTERFACE
interface MetricsInterface {
public function count($context, $source, $value, $metric);
public function decrement($context, $source, $metric);
public function endMemoryProfile($context, $source, $metric);
public function endTiming($context, $source, $metric);
public function gauge($context, $source, $value, $metric);
public function increment($context, $source, $metric);
public function memory($context, $source, $memory, $metric);
public function set($context, $source, $value, $metric);
public function setNamespace($namespace);
public function startMemoryProfile($context, $source, $metric);
public function startTiming($context, $source, $metric);
public function time($context, $source, Closure $func, $metric);
public function timing($context, $source, $value, $metric);
}
METRICS FOR A/B TESTING
MORE APPLICATIONS
Release Toggles
Timed Releases
Operations Toggles
Experiments
Permissions?
COMPLEXITY
TRADITIONAL FEATURE FLAGS
PROS
Eliminate Long Lived Branches
Disable Problematic Code
CONS
Complexity Bump
All Or Nothing
COHORT FEATURE FLAGS
PROS
Eliminate Long Lived Branches
Disable Problematic Code
Roll Out Features
A/B Testing
CONS
Complexity Bump
All Or Nothing
SWIVEL FEATURE FLAGS
PROS
Eliminate Long Lived Branches
Disable Problematic Code
Roll Out Features
A/B Testing
Built In Logging
Built In Metrics
CONS
Complexity Bump
SWIVELJS
swivel
.forFeature('AwesomeSauce')
.addBehavior('Spicy', this.getSpicyFormula)
.addBehavior('Saucy', this.getSaucyFormula)
.defaultBehavior(this.getFormula)
.execute();
COMMUNITY PACKAGES
ZF2 module: https://github.com/mrferos/mrf-swivel
Yii extension: https://github.com/DanaLuther/yii-swivel
Yii 2.0 extension: https://github.com/DanaLuther/yii2-swivel
CakePHP 2 Plugin: https://github.com/zumba/swivel-cake
ROADMAP
Bucket Ranges
Con gurable Granularity
CakePHP 3.0 Plugin
QUESTIONS?
GO FORTH AND TOGGLE!
Twitter: @young_steveo
Swivel: https://github.com/zumba/swivel
Joindin: https://legacy.joind.in/17583
THANKS!

Feature Flags Are Flawed: Let's Make Them Better - DPC

  • 1.
    FEATURE FLAGS ARE FLAWED LET'SMAKE THEM BETTER Created by Stephen Young / @young_steveo
  • 3.
    FEATURE FLAG? I've seena lot of names for this concept tossed around: feature bits, ags, ippers, switches and the like. There seems no generally accepted name yet. — Martin Fowler
  • 4.
    SIMPLE TOGGLE class AmazingRecipe { publicfunction getSauce() { $useNewRecipe = false; // $useNewRecipe = true; if ($useNewRecipe) { // new formula here } else { // original code here } } }
  • 5.
    TRADITIONAL FEATURE FLAGS ARE… FLAWED ...ACTUALLYPRETTY USEFUL AND NOT REALLY FLAWED PER SE, BUT THEY COME WITH A FEW CHALLENGES AND I THINK WE CAN IMPROVE THE MODEL, SO LET'S MAKE THEM BETTER
  • 6.
    SIMPLE TOGGLE class AmazingRecipe { publicfunction getSauce() { $useNewRecipe = false; // $useNewRecipe = true; if ($useNewRecipe) { // new formula here } else { // original code here } } } ❌ ❌ Not Testable
  • 7.
    SIMPLE TOGGLE V2 classAmazingRecipe { public function getSauce() { if ($this->useNewRecipe()) { // new formula here } else { // original code here } } public function useNewRecipe() { return false; // true } } ← ← Can Be Stubbed
  • 8.
    MOCK TOGGLE /** * @dataProvidernewFormulaProvider */ public function testGetSauce($useNew, $sauce) { $sut = $this->getMock('AmazingRecipe', ['useNewRecipe']); $sut->expects($this->once()) ->method('useNewRecipe') ->will($this->returnValue($useNew)); $this->assertEquals($sauce, $sut->getSauce()); } public function newFormulaProvider() { return [ [ false, 'old Recipe' ], [ true, 'new Recipe' ] ]; }
  • 9.
    SIMPLE TOGGLE V2 classAmazingRecipe { public function getSauce() { if ($this->useNewRecipe()) { // new formula here } else { // original code here } } public function useNewRecipe() { return false; // true } } ❌❌❌ Not Maintainable Not Con gurable Not My Concern
  • 10.
    FEATURE ROUTER class AmazingRecipe { publicfunction getSauce() { if ($this->useNewRecipe()) { // new formula here } else { // original code here } } public function useNewRecipe() { return Flags::enabled('AmazingRecipie.NewSauceFormula'); } }
  • 11.
    FEATURE ROUTER final classFlags { protected static $map = []; public static function enabled($key) { if (empty(static::$map)) { // hydrate map } return !empty(static::$map[$key]); } }
  • 12.
    TRADITIONAL FEATURE FLAGSYSTEM Curb Long-Lived Feature Branches Easy To Use (If This Then That) Testable
  • 13.
    TONS OF APPLICATIONS ReleaseToggles - change frequently, short lived Timed Releases Operations Toggles - sometimes long lived, rarely change
  • 14.
  • 15.
    CYCLOMATIC COMPLEXITY public functiongetSauce() { if ($this->useNewRecipe()) { if ($this->testSpicyVersion()) { // add spice } if ($this->fixEnabled()) { // fix bug in new code } } else { // original code here if ($this->fixEnabled()) { // fix bug in old code } } } Complexity: 5
  • 16.
  • 17.
  • 18.
    COHORTS […] a cohortis a group of subjects who have shared a particular event together during a particular time span. — Wikipedia
  • 19.
    MORE CONDITIONS? if ( Flags::enabled('SomeFeature')&& $user->canSeeFeature('SomeFeature') ) { // execute feature code } class User { public function canSeeFeature($feature) { // check the db or user session? } }
  • 20.
    INTRODUCING SWIVEL Swivel canenable features for a subset of users. No more complex control ow; Swivel takes care of determining code paths.
  • 21.
    BASIC CONCEPTS 1. Segmentusers into 10 cohorts. Swivel calls these Buckets. 2. Associate a Feature to a number of active Buckets. 3. Write code that runs when a particular Feature is enabled. Swivel calls these Behaviors.
  • 22.
    SEGMENT USERS INTOBUCKETS id user_id bucket_id 1 160 6 2 956 2 3 189 7 4 412 2
  • 23.
    ASSOCIATE FEATURES TOBUCKETS id slug buckets 1 "AwesomeSauce" "[ ]" 2 "AwesomeSauce.Spicy" "[1,2]" 3 "AwesomeSauce.Saucy" "[3,4]"
  • 24.
    BOOTSTRAP $bucket = 5;// From Session or DB $map = [ 'AwesomeSauce' => [1,2,3,4], 'AwesomeSauce.Spicy' => [1,2], 'AwesomeSauce.Saucy' => [3,4] ]; $config = new ZumbaSwivelConfig($map, $bucket); $swivel = new ZumbaSwivelManager($config);
  • 25.
    TOGGLE EXAMPLE class AmazingRecipe { publicfunction __construct(ZumbaSwivelManager $swivel) { $this->swivel = $swivel; } public function getSauce() { return $this->swivel->forFeature('AwesomeSauce') ->addBehavior('Spicy', [$this, 'getSpicyFormula']) ->addBehavior('Saucy', [$this, 'getSaucyFormula']) ->defaultBehavior([$this, 'getFormula']) ->execute(); } protected function getSpicyFormula() { } protected function getSaucyFormula() { } protected function getFormula() { } }
  • 26.
    SHORTHAND EXAMPLE class AmazingRecipe { publicfunction __construct(ZumbaSwivelManager $swivel) { $this->swivel = $swivel; } public function getSauce() { return $this->swivel->invoke( 'AwesomeSauce.New', [$this, 'getNewSauce'], [$this, 'getOldSauce'] ); } }
  • 27.
    METRICS FOR CANARYDEPLOYMENTS
  • 28.
    SWIVEL LOGGING PSR-3 LOGGERAWARE $config = new ZumbaSwivelConfig($map, $bucket, $psr3Logger); // or $config->setLogger($psr3Logger);
  • 29.
    SWIVEL METRICS STATSD STYLEMETRICS INTERFACE interface MetricsInterface { public function count($context, $source, $value, $metric); public function decrement($context, $source, $metric); public function endMemoryProfile($context, $source, $metric); public function endTiming($context, $source, $metric); public function gauge($context, $source, $value, $metric); public function increment($context, $source, $metric); public function memory($context, $source, $memory, $metric); public function set($context, $source, $value, $metric); public function setNamespace($namespace); public function startMemoryProfile($context, $source, $metric); public function startTiming($context, $source, $metric); public function time($context, $source, Closure $func, $metric); public function timing($context, $source, $value, $metric); }
  • 30.
  • 31.
    MORE APPLICATIONS Release Toggles TimedReleases Operations Toggles Experiments Permissions?
  • 32.
  • 33.
    TRADITIONAL FEATURE FLAGS PROS EliminateLong Lived Branches Disable Problematic Code CONS Complexity Bump All Or Nothing
  • 34.
    COHORT FEATURE FLAGS PROS EliminateLong Lived Branches Disable Problematic Code Roll Out Features A/B Testing CONS Complexity Bump All Or Nothing
  • 35.
    SWIVEL FEATURE FLAGS PROS EliminateLong Lived Branches Disable Problematic Code Roll Out Features A/B Testing Built In Logging Built In Metrics CONS Complexity Bump
  • 36.
  • 37.
    COMMUNITY PACKAGES ZF2 module:https://github.com/mrferos/mrf-swivel Yii extension: https://github.com/DanaLuther/yii-swivel Yii 2.0 extension: https://github.com/DanaLuther/yii2-swivel CakePHP 2 Plugin: https://github.com/zumba/swivel-cake
  • 38.
    ROADMAP Bucket Ranges Con gurableGranularity CakePHP 3.0 Plugin
  • 39.
  • 40.
    GO FORTH ANDTOGGLE! Twitter: @young_steveo Swivel: https://github.com/zumba/swivel Joindin: https://legacy.joind.in/17583 THANKS!