-
Notifications
You must be signed in to change notification settings - Fork 119
Update README file for variants #264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
898630f
89c13ad
12728da
18d63ea
57373e1
6ee9de8
e0c5e86
ce9d667
8bd7e87
43932ff
7157af1
808d015
91a3299
c9ce607
c21b4a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ Here are some of the benefits of using this library: | |
| * [Built-in Feature Filters](#built-in-Feature-Filters) | ||
| * [Targeting](#targeting) | ||
| * [Targeting Exclusion](#targeting-exclusion) | ||
| * [Variants](#variants) | ||
| * [Caching](#caching) | ||
| * [Custom Feature Providers](#custom-feature-providers) | ||
|
|
||
|
|
@@ -139,6 +140,25 @@ A `RequirementType` of `All` changes the traversal. First, if there are no filte | |
|
|
||
| In the above example, `FeatureW` specifies a `RequirementType` of `All`, meaning all of it's filters must evaluate to true for the feature to be enabled. In this case, the feature will be enabled for 50% of users during the specified time window. | ||
|
|
||
| ### Status | ||
|
|
||
|
|
||
| `Status` is an optional property of a feature flag that controls how a flag's enabled state is evaluated. By default, the status of a flag is `Conditional`, meaning that feature filters should be evaluated to determine if the flag is enabled. If the `Status` of a flag is set to `Disabled` then feature filters are not evaluated and the flag is always considered to be disabled. | ||
|
|
||
|
|
||
| ``` | ||
| "FeatureX": { | ||
| "Status": "Disabled", | ||
| "EnabledFor": [ | ||
| { | ||
| "Name": "AlwaysOn" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| In this example, even though the `AlwaysOn` filter would normally always make the feature enabled, the `Status` property is set to `Disabled`, so this feature will always be disabled. | ||
|
|
||
| ### Referencing | ||
|
|
||
| To make it easier to reference these feature flags in code, we recommend to define feature flag variables like below. | ||
|
|
@@ -636,6 +656,157 @@ When defining an Audience, users and groups can be excluded from the audience. T | |
|
|
||
| In the above example, the feature will be enabled for users named `Jeff` and `Alicia`. It will also be enabled for users in the group named `Ring0`. However, if the user is named `Mark`, the feature will be disabled, regardless if they are in the group `Ring0` or not. Exclusions take priority over the rest of the targeting filter. | ||
|
|
||
| ## Variants | ||
|
|
||
| When new features are added to an application, there may come a time when a feature has multiple different proposed design options. A common solution for deciding on a design is some form of A/B testing, which involves providing a different version of the feature to different segments of the user base and choosing a version based on user interaction. In this library, this functionality is enabled by representing different configurations of a feature with variants. | ||
|
|
||
| Variants enable a feature flag to become more than a simple on/off flag. A variant represents a value of a feature flag that can be a string, a number, a boolean, or even a configuration object. A feature flag that declares variants should define under what circumstances each variant should be used, which is covered in greater detail in the [Allocating a Variant](./README.md#allocating-a-variant) section. | ||
|
|
||
| ``` C# | ||
| public class Variant | ||
| { | ||
| /// <summary> | ||
| /// The name of the variant. | ||
| /// </summary> | ||
| public string Name { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// The configuration of the variant. | ||
| /// </summary> | ||
| public IConfigurationSection Configuration { get; set; } | ||
| } | ||
| ``` | ||
|
|
||
| ### Getting a Feature's Variant | ||
|
|
||
| A feature's variant can be retrieved using the `IVariantFeatureManager`'s `GetVariantAsync` method. | ||
|
|
||
| ``` C# | ||
| … | ||
| IVariantFeatureManager featureManager; | ||
| … | ||
| Variant variant = await featureManager.GetVariantAsync(MyFeatureFlags.FeatureU); | ||
|
|
||
| IConfigurationSection variantConfiguration = variant.Configuration; | ||
|
|
||
| // Do something with the resulting variant and its configuration | ||
| ``` | ||
|
|
||
| ### Setting a Variant's Configuration | ||
|
|
||
| For each of the variants in the `Variants` property of a feature, there is a specified configuration. This can be set using either the `ConfigurationReference` or `ConfigurationValue` properties. `ConfigurationReference` is a string path that references a section of the current configuration that contains the feature flag declaration. `ConfigurationValue` is an inline configuration that can be a string, number, boolean, or configuration object. If both are specified, `ConfigurationValue` is used. If neither are specified, the returned variant's `Configuration` property will be null. | ||
|
|
||
| ``` | ||
| "Variants": [ | ||
| { | ||
| "Name": "Big", | ||
| "ConfigurationReference": "ShoppingCart:Big" | ||
| }, | ||
| { | ||
| "Name": "Small", | ||
| "ConfigurationValue": { | ||
| "Size": 300 | ||
| } | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| ### Allocating a Variant | ||
|
|
||
| The process of allocating a variant to a specific feature is determined by the `Allocation` property of the feature. | ||
|
|
||
| ``` | ||
| "Allocation": { | ||
| "DefaultWhenEnabled": "Small", | ||
| "DefaultWhenDisabled": "Small", | ||
| "User": [ | ||
| { | ||
| "Variant": "Big", | ||
| "Users": [ | ||
| "Marsha" | ||
| ] | ||
| } | ||
| ], | ||
| "Group": [ | ||
| { | ||
| "Variant": "Big", | ||
| "Groups": [ | ||
| "Ring1" | ||
| ] | ||
| } | ||
| ], | ||
| "Percentile": [ | ||
| { | ||
| "Variant": "Big", | ||
| "From": 0, | ||
| "To": 10 | ||
| } | ||
| ], | ||
| "Seed": "13973240" | ||
| }, | ||
| "Variants": [ | ||
| { | ||
| "Name": "Big", | ||
| "ConfigurationReference": "ShoppingCart:Big" | ||
| }, | ||
| { | ||
| "Name": "Small", | ||
| "ConfigurationValue": "300px" | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| The `Allocation` setting of a feature flag has the following properties: | ||
|
|
||
| | Property | Description | | ||
| | ---------------- | ---------------- | | ||
| | `DefaultWhenDisabled` | Specifies which variant should be used when a variant is requested while the feature is considered disabled. | | ||
| | `DefaultWhenEnabled` | Specifies which variant should be used when a variant is requested while the feature is considered enabled and no variant was allocated to the user. | | ||
| | `User` | Specifies a variant and a list of users for which that variant should be used. | | ||
| | `Group` | Specifies a variant and a list of groups the current user has to be in for that variant to be used. | | ||
| | `Percentile` | Specifies a variant and a percentage range the user's calculated percentage has to fit into for that variant to be used. | | ||
| | `Seed` | The value which percentage calculations for `Percentile` are based on. The percentage calculation for a specific user will be the same across all features if the same `Seed` value is used. If no `Seed` is specified, then a default seed is created based on the feature name. | | ||
|
|
||
| In the above example, if the feature is not enabled, `GetVariantAsync` would return the variant allocated by `DefaultWhenDisabled`, which is `Small` in this case. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be worth it to call out each property of The
And then go into the specific behavior of this example. |
||
|
|
||
| If the feature is enabled, the feature manager will check the `User`, `Group`, and `Percentile` allocations in that order to allocate a variant for this feature. If the user being evaluated is named `Marsha`, in the group named `Ring1`, or the user happens to fall between the 0 and 10th percentile calculated with the given `Seed`, then the specified variant is returned for that allocation. In this case, all of these would return the `Big` variant. If none of these allocations match, the `DefaultWhenEnabled` variant is returned, which is `Small`. | ||
|
|
||
| Allocation logic is similar to the [Microsoft.Targeting](./README.md#MicrosoftTargeting) feature filter, but there are some parameters that are present in targeting that aren't in allocation, and vice versa. The outcomes of targeting and allocation are not related. | ||
|
|
||
| ### Overriding Enabled State with a Variant | ||
|
|
||
| You can use variants to override the enabled state of a feature flag. This gives variants an opportunity to extend the evaluation of a feature flag. If a caller is checking whether a flag that has variants is enabled, then variant allocation will be performed to see if an allocated variant is set up to override the result. This is done using the optional variant property `StatusOverride`. By default, this property is set to `None`, which means the variant doesn't affect whether the flag is considered enabled or disabled. Setting `StatusOverride` to `Enabled` allows the variant, when chosen, to override a flag to be enabled. Setting `StatusOverride` to `Disabled` provides the opposite functionality, therefore disabling the flag when the variant is chosen. A feature with a `Status` of `Disabled` cannot be overridden. | ||
|
|
||
| ``` | ||
| "Allocation": { | ||
| "Percentile": [{ | ||
| "Variant": "On", | ||
| "From": 10, | ||
| "To": 20 | ||
| }], | ||
| "DefaultWhenEnabled": "Off", | ||
| "Seed": "Enhanced-Feature-Group" | ||
| }, | ||
| "Variants": [ | ||
| { | ||
| "Name": "On", | ||
| "ConfigurationValue": true | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this example, the ConfigurationValue doesn't matter much. Can we remove it for both variants? |
||
| }, | ||
| { | ||
| "Name": "Off", | ||
| "ConfigurationValue": false, | ||
| "StatusOverride": "Disabled" | ||
| } | ||
| ], | ||
| "EnabledFor": [ | ||
| { | ||
| "Name": "AlwaysOn" | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| In the above example, the feature is enabled by the `AlwaysOn` filter. If the current user is in the calculated percentile range of 10 to 20, then the `On` variant is returned. Otherwise, the `Off` variant is returned and because `StatusOverride` is equal to `Disabled`, the feature will now be considered disabled. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hope we can explain a little more about the scenarios where this property can be useful. Can we add a sentence like below at the beginning of this paragraph? If you are using a feature flag with binary variants, the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the example should be immediately followed by its explanation, could we add something similar to this sentence just before the example maybe? In a separate paragraph from the introduction?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. That works. |
||
|
|
||
amerjusupovic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ## Caching | ||
|
|
||
| Feature state is provided by the IConfiguration system. Any caching and dynamic updating is expected to be handled by configuration providers. The feature manager asks IConfiguration for the latest value of a feature's state whenever a feature is checked to be enabled. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the Seed is not specified? Do we use a default value?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I should mention this, we create a new string using the feature name and use that instead of the
Seedvalue. I guess that would be the default value.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
featurename + "\nallocation"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would consider it appropriate for us to mention that if seed is omitted, then a default seed based off the flag name is used. But I would say away from mentioning our exact implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it will be great to clarify.