feature flags are flawed: let's make them better
TRANSCRIPT
FEATURE FLAGS ARE FLAWED
LET'S MAKE THEM BETTERCreated by Stephen Young / @young_steveo
FEATURE FLAG?I've seen a lot of names for this concept tossed
around: feature bits, �ags, �ippers, switchesand the like. There seems no generally
accepted name yet.
— Martin Fowler
SIMPLE TOGGLEclass AmazingRecipe { public function getSauce() {
$useNewRecipe = false;
// $useNewRecipe = true;
if ($useNewRecipe) { // new formula here } else { // original code here } } }
TRADITIONALFEATURE 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 TOGGLEclass AmazingRecipe { public function getSauce() { $useNewRecipe = false; // $useNewRecipe = true;
if ($useNewRecipe) { // new formula here } else { // original code here } } }
❌❌
Not Testable
SIMPLE TOGGLE V2class 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 V2class AmazingRecipe { public function getSauce() { if ($this->useNewRecipe()) { // new formula here } else { // original code here } } public function useNewRecipe() { return false; // true } }
❌❌❌
Not MaintainableNot Con�gurableNot My Concern
FEATURE ROUTERclass AmazingRecipe { public function getSauce() { if ($this->useNewRecipe()) { // new formula here } else { // original code here } } public function useNewRecipe() { return Flags::enabled('AmazingRecipie.NewSauceFormula'); } }
FEATURE ROUTERfinal class Flags { protected static $map = []; public static function enabled($key) { if (empty(static::$map)) { // hydrate map } return !empty(static::$map[$key]); } }
TRADITIONAL FEATURE FLAG SYSTEMCurb Long-Lived Feature Branches
Easy To Use (If This Then That)
Testable
Tons of Applications
Continuous DeliveryTimed ReleasesOperations Toggles
WHAT'S NOT TO LIKE?
CYCLOMATIC COMPLEXITYpublic 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 haveshared 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 SWIVELSwivel can enable features for a subset of users.
No more complex control �ow; Swivel takes care ofdetermining code paths.
BASIC CONCEPTS1. 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.
4. Group Features under common Parent Feature to togglemultiple at once.
NewExperienceNewExperience.NavLinksNewExperience.DashboardNewExperience.Pro�le
SEGMENT USERS INTO BUCKETSid user_id bucket_id
1 160 6
2 956 2
3 189 7
4 412 2
ASSOCIATE FEATURES TO BUCKETSid slug buckets
1 "AwesomeSauce" "1,2,3,4"
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 \Zumba\Swivel\Config($map, $bucket);
$swivel = new \Zumba\Swivel\Manager($config);
TOGGLE EXAMPLEclass AmazingRecipe { public function __construct(\Zumba\Swivel\Manager $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() { } }
METRICS FOR CANARY DEPLOYMENTS
SWIVEL LOGGINGPSR-3 LOGGER AWARE
$config = new \Zumba\Swivel\Config($map, $bucket, $psr3Logger);
// or
$config->setLogger($psr3Logger);
SWIVEL METRICSSTATSD 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
COMPLEXITY
TRADITIONAL FEATURE FLAGSPROS
Eliminate Long Lived BranchesDisable Problematic Code
CONSComplexity BumpAll Or Nothing
COHORT FEATURE FLAGSPROS
Eliminate Long Lived BranchesDisable Problematic CodeRoll Out FeaturesA/B Testing
CONSComplexity BumpAll Or Nothing
SWIVEL FEATURE FLAGSPROS
Eliminate Long Lived BranchesDisable Problematic CodeRoll Out FeaturesA/B TestingBuilt In LoggingBuilt In Metrics
CONSComplexity Bump
QUESTIONS?
GO FORTH AND TOGGLE!Twitter: @young_steveo
Lanyrd: http://lanyrd.com/2016/syntaxcon/sfbqxr
Swivel: https://github.com/zumba/swivel
Swiveljs: https://github.com/zumba/swiveljs
Swivel Cake: https://github.com/zumba/swivel-cake
THANKS!