Building blocks
Features
Features are the building blocks of creating traditional boolean feature flags and more advanced multivariate experiments, along with variables.
Evaluations#
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 (
boolean
): its own on/off status - Variation (
string
): a string value if you have a/b tests running - Variables: a set of key/value pairs (if any)
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
:
description: Sidebartags: - allbucketBy: userIdrules: production: - key: everyone segments: '*' # `*` means everyone percentage: 100 # rolled out 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.
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 and you are managing all the features from the same Featurevisor project.
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 values of a feature are assigned to a user as they get rolled out gradually.
Single attribute#
If the user's ID is always known when the particular feature is evaluated, it makes sense to use that as the bucketBy
value.
# ...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 in context, the same value(s) of the feature will be consistently evaluated for that particular user.
Anonymous users#
If the user is anonymous, you can consider using deviceId
or any other unique identifier that is available in the context instead.
# ...bucketBy: deviceId
Combining attributes#
If you want to bucket users against multiple attributes together, you can do as follows:
# ...bucketBy: - organizationId - userId
Alternative attribute#
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.
Rules#
A feature can have multiple rollout rules for each environment.
By environment#
The environment keys are based on your project configuration. Read more in Configuration and Environments.
# ...rules: staging: - key: everyone segments: '*' percentage: 100 production: - key: everyone segments: '*' percentage: 100
Rule key#
Each rule must have a unique key
value among sibling rules within that environment.
This is needed to maintain consistent bucketing as we increase our rollout percentage over time, ensuring the same user gets the same feature value every time they are evaluated against the same context.
# ...rules: production: - key: nl segments: netherlands percentage: 50 - key: everyone segments: '*' # everyone percentage: 100
The first rule matched always wins when features are evaluated by the SDKs against provided context.
Segments#
Targeting your audience is one of the most important things when rolling out features. You can do that with reusable segments.
Targeting everyone#
If we wish to roll out a feature to everyone, we can use the *
asterisk in segments
property inside a rule:
# ...rules: production: - key: everyone segments: '*' percentage: 100
Specific segment#
If we wish to roll out a feature to a specific segment:
# ...rules: production: - key: de segments: germany # referencing segments/germany.yml percentage: 100 # any value between 0 and 100 (inclusive)
Complex#
We can combine and
, or
, and not
operators to create complex segments:
With and
operator:#
# ...rules: production: - key: de+iphone # any unique string is fine here # targeting: iphone users in germany segments: and: - germany - iphoneUsers percentage: 100
With or
operator:#
# ...rules: production: - key: nl-or-de # targeting: users from either The Netherlands or Germany segments: or: - netherlands - germany percentage: 100
With not
operator:#
# ...rules: production: - key: not-de # targeting: users from everywhere except Germany segments: not: - germany percentage: 100
Combining multiple operators#
Combining and
, or
, and not
operators:
# ...rules: production: - 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
Nested operators#
You can also nest and
, or
, and not
operators:
# ...rules: production: - key: '1' segments: - and: - iphoneUsers - adultUsers - or: - netherlands - germany percentage: 100
Percentage#
The percentage
property is used to determine what percentage of users matching the segments of the particular rule will see this feature as enabled:
# ...rules: production: - key: everyone segments: '*' percentage: 100
You can choose a number between 0
and 100
(inclusive), with up to 2 decimal places.
Rule description#
You can also describe each rule with an optional description
property. This is useful for documentation purposes:
# ...rules: production: - key: everyone description: Rollout to everyone in production segments: '*' percentage: 100
Variations#
A feature can have multiple variations if you wish to run A/B test experiments.
Weights#
Each variation must have a unique string value with their own weights (out of 100):
# ...variations: - value: control weight: 50 - value: treatment weight: 50
The sum of all variations' weights must be 100.
You can have up to 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.
Disabled variation value#
If the feature is evaluated as disabled, then its variation will evaluate as null
by default.
You can change this behaviour by setting disabledVariationValue
property in the feature:
# ...disabledVariationValue: controlvariations: - value: control weight: 50 - value: treatment weight: 50
Overriding variation value#
You can also override the variation of a feature for a specific rule:
# ...rules: production: - key: nl segments: netherlands percentage: 100 variation: control
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.
Overriding variation weights#
The weights of variations as defined in variations
is honoured by all rules by default.
There might be cases where you want to override the weights of variations just for a specific rule. You can do that by defining variationWeights
property in the rule:
# ...rules: production: - key: everyone segments: '*' percentage: 100 variationWeights: control: 70 treatment: 30
Variation description#
You can also describe each variation with an optional description
property for documentation purposes:
# ...variations: - value: control description: Default experiment for all users weight: 50 - value: treatment description: The new sidebar design that we are testing weight: 50
Variables#
Variables are really powerful, and they allow you to use Featurevisor as your application's runtime configuration management tool.
Schema#
Before assigning variable values, we must define the schema of our variables in the feature:
# ...variablesSchema: bgColor: type: string defaultValue: red
Like variations, variables can also have a description
property for documentation purposes.
You can read more about using Featurevisor for remote configuration needs here.
Default when disabled#
If the feature itself is evaluated as disabled, its variables will evaluate as null
by default.
If you want to serve the variable's default value when the feature is disabled, you can set useDefaultWhenDisabled: true
:
# ...variablesSchema: bgColor: type: string defaultValue: red useDefaultWhenDisabled: true
Disabled variable value#
If you want a specific variable value to be served instead of the default one when the feature itself is disabled, you can set disabledValue
property:
# ...variablesSchema: bgColor: type: string defaultValue: red disabledValue: purple
Variable description#
You can also describe each variable with an optional description
property for documentation purposes:
# ...variablesSchema: bgColor: type: string description: Background colour of the sidebar defaultValue: red
Supported types#
These types of variables are allowed:
string
boolean
integer
double
array
(of strings)object
(flat objects only)json
(any valid JSON in stringified form)
string
#
# ...variablesSchema: bgColor: type: string defaultValue: red
boolean
#
# ...variablesSchema: showSidebar: type: boolean defaultValue: false
integer
#
# ...variablesSchema: position: type: integer defaultValue: 1
double
#
# ...variablesSchema: amount: type: double defaultValue: 9.99
array
#
# ...variablesSchema: acceptedCards: type: array defaultValue: - visa - mastercard
object
#
# ...variablesSchema: hero: type: object defaultValue: title: Welcome subtitle: Welcome to our website
json
#
# ...variablesSchema: hero: type: json defaultValue: '{"title": "Welcome", "subtitle": "Welcome to our website"}'
Overriding variables#
From rules#
You can override variable values for specific rules:
# ...rules: production: - key: nl segments: netherlands percentage: 100 variables: bgColor: orange
From variations#
We can assign values to the variables inside variations:
# ...variations: - value: control weight: 50 - value: treatment weight: 50 variables: bgColor: 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.
Further overriding from variation#
# ...variations: # ... - value: treatment weight: 100 variableOverrides: # bgColor should be orange if `netherlands` segment is matched bgColor: - segments: netherlands value: orange # for everyone else in `treatment` variation, it should be blue variables: bgColor: blue
If you want to embed overriding conditions directly within variations:
# ...variations: # ... - value: treatment weight: 100 variableOverrides: bgColor: - conditions: - attribute: country operator: equals value: nl value: orange variables: bgColor: blue
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
.
Required as enabled#
Given the checkoutPromo
feature is dependent on the checkoutRedesign
feature, we can express that in YAML as follows:
description: Checkout promotags: - allbucketBy: userIdrequired: - 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.
Required with variation#
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:
description: Checkout promotags: - allbucketBy: userIdrequired: # checking only if checkoutRedesign is enabled - checkoutRedesign # require the feature to be evaluated with 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.
With conditions#
# ...force: production: - 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
With segments#
Instead of conditions
above, you can also use segments
for forcing variations and variables.
# ...force: production: - segments: QATeam # enable or disable it enabled: true # forced variation variation: treatment # variables can also be forced variables: bgColor: purple
You can see our use case covering this functionality in testing in production guide.
Unlike rules, forcing evaluations do not require key
and percentage
properties.
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.
Similarly, variables can also be deprecated:
# ...variablesSchema: bgColor: type: string defaultValue: red deprecated: true # mark as deprecated
This is done to help notify the developers to stop using the affected feature or its variable 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:
# ...# this optional property tells Featurevisor# to not include this feature config# when generating datafiles# for this specific environmentexpose: production: false
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 tagstags: - web - ios - android# this optional property tells Featurevisor# to include this feature config# when generating datafiles# for only these specific tagsexpose: production: - web - ios # skipping `android` here
Ideally we never wish to keep expose
property in our definitions, and it is only meant to serve our short term needs especially when we might be migrating from another feature management tool to Featurevisor.