The document discusses improving feature flags by introducing the concept of Swivel. Swivel allows features to be enabled for subsets of users by segmenting users into cohorts called "buckets" and associating features with certain buckets. This allows for more granular control over features than a simple on/off flag and enables use cases like canary deployments and A/B testing. The document outlines the basic concepts of Swivel and provides examples of how it can be implemented and used to toggle features for different buckets of users in a more maintainable way than traditional feature flags.
3. 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
4. SIMPLE TOGGLE
class AmazingRecipe
{
public function getSauce()
{
$useNewRecipe = false;
// $useNewRecipe = true;
if ($useNewRecipe) {
// new formula here
} else {
// original code here
}
}
}
6. SIMPLE TOGGLE
class AmazingRecipe
{
public function getSauce()
{
$useNewRecipe = false;
// $useNewRecipe = true;
if ($useNewRecipe) {
// new formula here
} else {
// original code here
}
}
}
❌
❌
Not Testable
7. 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
9. 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
10. 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');
}
}
11. FEATURE ROUTER
final class Flags
{
protected static $map = [];
public static function enabled($key)
{
if (empty(static::$map)) {
// hydrate map
}
return !empty(static::$map[$key]);
}
}
12. TRADITIONAL FEATURE FLAG SYSTEM
Curb Long-Lived Feature Branches
Easy To Use (If This Then That)
Testable
13. TONS OF APPLICATIONS
Release Toggles - change frequently, short lived
Timed Releases
Operations Toggles - sometimes long lived, rarely change
15. 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
18. COHORTS
[…] a cohort is 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 can enable features for a subset of users.
No more complex control ow; Swivel takes care of
determining code paths.
21. 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.
22. SEGMENT USERS INTO BUCKETS
id user_id bucket_id
1 160 6
2 956 2
3 189 7
4 412 2
23. ASSOCIATE FEATURES TO BUCKETS
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
{
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() { }
}
26. 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']
);
}
}
28. SWIVEL LOGGING
PSR-3 LOGGER AWARE
$config = new ZumbaSwivelConfig($map, $bucket, $psr3Logger);
// or
$config->setLogger($psr3Logger);
29. 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);
}
34. COHORT FEATURE FLAGS
PROS
Eliminate Long Lived Branches
Disable Problematic Code
Roll Out Features
A/B Testing
CONS
Complexity Bump
All Or Nothing
35. 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