Building blocks
Features
Features are the building blocks of creating traditional boolean feature flags and more advanced multivariate experiments.
The goal of creating a feature is to be able to evaluate its values in your application with the provided SDKs. The evaluated values can be either its:
- flag: its own on/off status
- variation: a string value if you have a/b tests running
- variables: a set of key/value pairs
Create a Feature
Let's say we have built a new sidebar in our application's UI, and we wish to roll it out gradually to our users.
We can do that by creating a new feature called sidebar
:
# features/sidebar.yml
description: Sidebar
tags:
- all
bucketBy: userId
environments:
production:
rules:
- key: "1"
segments: "*"
percentage: 100
This is the smallest possible definition of a feature in Featurevisor.
Quite a few are happening there. We will go through each of the properties from the snippet above and more in the following sections.
Description
This is for describing what the feature is about, and meant to be used by the team members who are working on the feature.
# features/sidebar.yml
description: Some human readable description of this particular feature
Tags
Tags are used to group features together. This helps your application load only the features that are relevant to the application itself.
Very useful when you have multiple applications targeting different platforms (like Web, iOS, Android) in your organization.
Array of tags are defined in the tags
property:
# ...
tags:
- all
- web
- ios
Read more about how tags are relevant in building datafiles per tag.
Bucketing
The bucketBy
property is used to determine how the feature will be bucketed. Meaning, how the variation of a feature is assigned to a user.
# ...
bucketBy: userId
Given we used userId
attribute as the bucketBy
value, it means no matter which application or device the user is using, as long as the userId
attribute's value is the same, the same value(s) of the feature will be consistently assigned to that particular user.
If you want to bucket users against multiple attributes together, you can do as follows:
# ...
bucketBy:
- organizationId
- userId
If you want to bucket users against first available attribute only, you can do as follows:
# ...
bucketBy:
or:
- userId
- deviceId
You can read more about bucketing concept here.
Variations
A feature can have multiple variations if you wish to run A/B tests. Each variation must have a different string value.
# ...
variations:
- value: control
weight: 50
- value: firstTreatment
weight: 25
- value: secondTreatment
weight: 25
The sum of all variations' weights must be 100.
You can have upto 2 decimal places for each weight.
Control variation
In the world of experimentation, the default variation is usually called the control
variation, and the second variation is called treatment
.
But you are free to name them however you want, and create as many variations as you want.
You can read more about experimentation here.
Environments
This is where we define the rollout rules for each environment.
# ...
environments:
staging:
rules:
- key: "1"
segments: "*"
percentage: 100
production:
rules:
- key: "1"
segments: "*"
percentage: 100
Each environment is keyed by its name, as seen above with staging
and production
environments.
The environment keys are based on your project configuration. Read more in Configuration.
Rules
Each environment can have multiple rollout rules.
Each rule must have a unique key
value among sibling rules within that environment, and this is needed to maintain consistent bucketing as we increase our rollout percentage for the feature over time.
# ...
environments:
production:
rules:
- key: "1"
segments: netherlands
percentage: 50
- key: "2"
segments: "*" # everyone
percentage: 100
The first rule matched always wins when features are evaluated by the SDKs.
Read more in Bucketing.
Overriding variation
You can also override the variation of a feature for a specific rule:
# ...
environments:
production:
rules:
- key: "1"
segments: netherlands
percentage: 100
variation: b
This is useful when you know the desired variation you want to stick to in a specific rule's segments, while continuing testing other variations in other rules.
Segments
Targeting your audience is one of the most important things when rolling out features. You can do that with segments.
Everyone
If we wish to roll out a feature to everyone, we can use the *
wildcard in segments
property:
# ...
environments:
production:
rules:
- key: "1"
segments: "*"
percentage: 100
Specific
If we wish to roll out a feature to a specific segment:
# ...
environments:
production:
rules:
- key: "1"
segments: germany
percentage: 100
Complex
We can combine and
, or
, and not
operators to create complex segments:
With and
operator:
# ...
environments:
production:
rules:
- key: "1"
# targeting: iphone users in germany
segments:
and:
- germany
- iphoneUsers
percentage: 100
With or
operator:
# ...
environments:
production:
rules:
- key: "1"
# targeting: users from either The Netherlands or Germany
segments:
or:
- netherlands
- germany
percentage: 100
With not
operator:
# ...
environments:
production:
rules:
- key: "1"
# targeting: users from everywhere except Germany
segments:
not:
- germany
percentage: 100
Combining and
, or
, and not
operators:
# ...
environments:
production:
rules:
- key: "1"
# targeting:
# - adult users with iPhone, and
# - from either The Netherlands or Germany, and
# - not newsletterSubscribers
segments:
- and:
- iphoneUsers
- adultUsers
- or:
- netherlands
- germany
- not:
- newsletterSubscribers
percentage: 100
You can also nest and
, or
, and not
operators:
# ...
environments:
production:
rules:
- key: "1"
segments:
- and:
- iphoneUsers
- adultUsers
- or:
- netherlands
- germany
percentage: 100
Variables
Variables are really powerful, and they allow you to use Featurevisor as your application's runtime configuration management tool.
Before assigning variable values, we must define the schema of our variables in the feature:
# ...
variablesSchema:
- key: bgColor
type: string
defaultValue: red
We can assign values to the variables inside variations:
# ...
variations:
- value: control
weight: 50
- value: treatment
weight: 50
variables:
- key: bgColor
value: blue
If users are bucketed in the treatment
variation, they will get the bgColor
variable value of blue
. Otherwise they will fall back to the default value of red
as defined in the variables schema.
Like variations, variables can also have a description
property for documentation purposes.
You can read more about using Featurevisor for remote configuration needs here.
Supported types
These types of variables are allowed:
string
boolean
double
integer
array
(of strings)object
(flat objects only)json
(any valid JSON in stringified form)
Overriding variables
You can override variable values for specific rules:
# ...
environments:
production:
rules:
- key: "1"
segments: netherlands
percentage: 100
variables:
bgColor: orange
Or, you can override within variations:
# ...
variations:
# ...
- value: treatment
weight: 100
variables:
- key: bgColor
value: blue
overrides:
- segments: netherlands
value: orange
If you want to embed overriding conditions directly within variations:
# ...
variations:
# ...
- value: treatment
weight: 100
variables:
- key: bgColor
value: blue
overrides:
- conditions:
- attribute: country
operator: equals
value: nl
value: orange
Variable types
Examples of each type of variable:
string
# ...
variablesSchema:
- key: bgColor
type: string
defaultValue: red
variations:
# ...
- value: treatment
weight: 100
variables:
- key: bgColor
value: blue
boolean
# ...
variablesSchema:
- key: showSidebar
type: boolean
defaultValue: false
variations:
# ...
- value: treatment
weight: 100
variables:
- key: showSidebar
value: true
integer
# ...
variablesSchema:
- key: position
type: integer
defaultValue: 1
variations:
# ...
- value: treatment
weight: 100
variables:
- key: position
value: 2
double
# ...
variablesSchema:
- key: amount
type: double
defaultValue: 9.99
variations:
# ...
- value: treatment
weight: 100
variables:
- key: amount
value: 4.99
array
# ...
variablesSchema:
- key: acceptedCards
type: array
defaultValue:
- visa
- mastercard
variations:
# ...
- value: treatment
weight: 100
variables:
- key: acceptedCards
value:
- visa
- amex
object
# ...
variablesSchema:
- key: hero
type: object
defaultValue:
title: Welcome
subtitle: Welcome to our website
variations:
# ...
- value: treatment
weight: 100
variables:
- key: hero
value:
title: Welcome to our website
subtitle: We are glad you are here
json
# ...
variablesSchema:
- key: hero
type: json
defaultValue: '{"title": "Welcome", "subtitle": "Welcome to our website"}'
variations:
# ...
- value: treatment
weight: 100
variables:
- key: hero
value: '{"title": "Welcome to our website", "subtitle": "We are glad you are here"}'
Required
A feature can be dependent on one or more other features. This is useful when you want to make sure that a feature is only allowed to continue its evaluation if the other required features are also evaluated as enabled first.
For example, let's say we have a new feature under development for redesigning the checkout flow of an e-commerce application. We can call it checkoutRedesign
.
And we have another feature called checkoutPromo
which is responsible for showing a promo code input field in the new redesigned checkout flow. We can call it checkoutPromo
.
Given the checkoutPromo
feature is dependent on the checkoutRedesign
feature, we can express that in YAML as follows:
# features/checkoutPromo.yml
description: Checkout promo
tags:
- all
bucketBy: userId
required:
- checkoutRedesign
# ...
This will make sure that checkoutPromo
feature can continue its evaluation by the SDKs if checkoutRedesign
feature is enabled against the same context first.
It is possible to have multiple features defined as required for a feature. Furthermore, you can also require the feature(s) to be evaluated as a specific variation:
# features/checkoutPromo.yml
description: Checkout promo
tags:
- all
bucketBy: userId
required:
# checking only if checkoutRedesign is enabled
- checkoutRedesign
# require the feature to be evaluated as a specific variation
- key: someOtherFeature
variation: treatment
# ...
If both the required features are evaluated as desired, the dependent feature checkoutPromo
will then continue with its own evaluation.
You can read more about managing feature dependencies here.
Force
You can force a feature to be enabled or disabled against custom conditions.
This is very useful when you wish to test something out quickly just for yourself in a specific environment without affecting any other users.
# ...
environments:
production:
rules:
# ...
force:
- conditions:
- attribute: userId
operator: equals
value: "123"
# enable or disable it
enabled: true
# forced variation
variation: treatment
# variables can also be forced
variables:
bgColor: purple
Instead of conditions
above, you can also use segments
for forcing variations and variables.
You can see our use case covering this functionality in testing in production guide.
Deprecating
You can deprecate a feature by setting deprecated: true
:
deprecated: true
# ...
Deprecating a feature will still include the feature in generated datafiles and SDKs will still be able to evaluate the feature, but evaluation will lead to showing a warning in the logs.
This is done to help notify the developers to stop using the affected feature without breaking the application.
Archiving
You can archive a feature by setting archived: true
:
archived: true
# ...
Doing so will exclude the feature from generated datafiles and SDKs will not be able to evaluate the feature.
Expose
In some cases, you may not want to expose a certain feature's configuration only in specific environments when generating the datafiles.
Exposure here means the inclusion of the feature's configuration in the generated datafile, irrespective of whether the feature is later evaluated as enabled or disabled.
This is different than:
- Archiving: because you only want to control the exposure of the feature's configuration in a specific environment, not all environments
- Deprecating: because deprecating a feature will still expose the configuration in all environments
- 0% rollout: because this will evaluate the feature as disabled as intended, but still expose the configuration in the datafiles which we do not want
To achieve that, we can use the expose
property when defining an environment's rules:
# ...
environments:
production:
# this optional property tells Featurevisor
# to not include this feature config
# when generating datafiles
# for this specific environment
expose: false
# even though we have rules defined here,
# the feature won't end up in production datafiles
rules:
- key: "1"
segments: "*"
percentage: 100
This technique is useful if you wish to test things out in a specific environment (like staging) without affecting rest of the environments (like production).
You can take things a bit further if you wish to expose the feature only for certain tags in an environment:
# ...
# imagine you already had these tags
tags:
- web
- ios
- android
environments:
production:
# this optional property tells Featurevisor
# to include this feature config
# when generating datafiles
# for only these specific tags
expose:
- web
- ios
# skipping `android` here
rules:
- key: "1"
segments: "*"
percentage: 100
Ideally we never wish to keep expose
property in our definitions, and it is only meant to serve our short term needs.