Implementations of a variants (experiments, mods) system. Allows for dynamic flag evaluation based on conditions.
JavaScript Go
Latest commit 2db3760 Dec 23, 2014 @andybons andybons Merge pull request #27 from Medium/andybons/races
Make a Registry safe to access from multiple goroutines.
Permalink
Failed to load latest commit information.
go Make a registry safe to access from multiple goroutines. Dec 22, 2014
nodejs
.gitignore
LICENSE.txt Adds README and license file. Jul 3, 2012
README.md Update spec, require operator for >1 condition. May 11, 2013

README.md

Variants

Background

In web applications it is common to provide varying experiences to unique sets of users. A flexible design should allow implementations of common patterns in web development like:

  • A/B testing
  • Experimental features
  • Trusted tester groups
  • Gradual feature rollouts

Overview

Variants provide an expressive way to define and conditionally modify experimental features, which can also be forcefully adjusted (for development).

Note that the following README only provides a general overview of variants, and is language independent. Currently, there are ports written for Node.js and Go. See the implementation-specific READMEs for more information.

To conditionally gate certain features, they must be protected by variant flags. Variant flags are globally unique strings that can point to a language primitive, array or object. Most commonly, variant flags are simple boolean values so that the below code is possible:

if (variants.getFlagValue('enable_product_access')) {
  throw Error('Authenticated failed.')
}

Design

  • Each service contains variants
  • Variants contain 0 or more conditions and 1 or more mods
  • Conditions evaluate the current request based on the condition type and values
  • Mods modify variant flags
  • Variant flags are checked in code to gate control flow

Variant

Variants are globally defined objects that may optionally modify values based on some conditions. All variants are evaluated on a per request basis, which means that they are scoped to request-based values such as: user ip, specific users, groups of users, query parameters, etc.

Variants must have an id and a list of conditions and mods. A variant must contain at least one mod to be valid.

variant: {
  required string id
  optional string conditional_operator
  optional condition[] conditions
  required mod[] mods
}

Condition

Conditions return true or false based on the current request object. If more than one condition is supplied, then the conditional_operator (either "OR" or "AND") must be supplied.

Below is a list of condition types:

USER_ID

User id is a condition that evaluates the given condition based on a list of usernames in the "values" field.

E.g.

{
  "type": "USER_ID",
  "values": [
    "somedude74",
    "anotherdude323",
    "hax0r1337"
  ]
}

USER_ID_MOD

User id mods use a hashed value of the current user’s username mapped onto a range from 0-99. It allows the properties "range_start" and "range_end", which contain values between 0-99 and range_end must be greater than range_start.

By default, this uses the unique user id of an authenticated user. However, the "cookie_type" field can be set to "NSID" to refer to unauthenticated users.

E.g.

{
  "type": "USER_ID_MOD",
  "values": [ 0, 9 ]
}

Note: This is useful for rolling out new features, such as to 1% -> 10% -> 50% -> 100% of users.

RANDOM

Random will randomly determine whether or not a given request is eligible for the variant.

E.g.

{
  "type": "RANDOM",
  "value": 0.25
}

Mod

Mods are triggered when the conditions are met on the given variant. The format of a mod is simply a key and a value. The key must refer to a global identifier for the variant flag.

Full spec

Spec in pseudo-protobuf format:

message Variants {
  repeated Variant variants;
}

message Variant {

  enum Operator {
    AND, // "AND"
    OR   // "OR"
  }

  // Unique identifier.
  required string id;

  // Readable description of the feature.
  optional string description;

  // Optional operator to evaluate the conditions.
  optional Operator conditional_operator;

  // List of conditions to evaluate.
  repeated Condition conditions;

  // List of mods to be triggered.
  repeated Mod mods;
}

message Condition {

  enum Type {
    RANDOM,
    USER_ID,
    USER_ID_MOD,
    USER_IP
  };

  // Type of condition.
  required Type type;

  // Single value.
  optional * value;

  // List of values.
  repeated * values;
}

message Mod {
  // Name of the variant flag to modify.
  required string flag;

  // Value to set.
  required * value;
}

Appendix

Contributing

Questions, comments, bug reports, and pull requests are all welcome. Submit them at the project on GitHub.

Bug reports that include steps-to-reproduce (including code) are the best. Even better, make them in the form of pull requests that update the test suite. Thanks!

Author

David Byttow supported by The Obvious Corporation.

License

Copyright 2012 The Obvious Corporation.

Licensed under the Apache License, Version 2.0. See the top-level file LICENSE.txt and (http://www.apache.org/licenses/LICENSE-2.0).