Skip to content

Conversation

@jimmyca15
Copy link
Member

@jimmyca15 jimmyca15 commented Jul 28, 2021

Dynamic Features

This PR adds a new paradigm named dynamic features, which are features that may have different variants. Any modification or improvement to an application can be considered a feature. When new features are being added to applications there may be multiple different designs. The different options for the design of a feature can be referred to as variants of the feature, and the feature itself can be referred to as a dynamic feature. A common pattern when rolling out dynamic features is to surface the different variants of a feature to different segments of a user base and to see how each variant is perceived. The most well received variant could be the one that gets rolled out to the entire user base, or if necessary the feature could be scrapped. There could be other reasons to expose different variants of a feature, for example using a different version every day of the week. The goal of this method is to establish a model that can help solve these common patterns that occur when rolling out features that can have different variants.

Consumption

Feature variant's are accessible through the IFeatureVariantManager interface.

public interface IFeatureVariantManager
{
    ValueTask<T> GetVariantAsync<T>(string feature, CancellationToken cancellationToken);

    ValueTask<T> GetVariantAsync<T, TContext>(string feature, TContext context, CancellationToken cancellationToken);
}

The variant manager performs a resolution process that takes the name of a feature and returns a strongly typed value to represent the variant's options.

The following steps are performed during the retrieval of a dynamic feature's variant

  1. Lookup the configuration of the specified dynamic feature to find the registered variants
  2. Assign one of the registered variants to be used.
  3. Resolve typed options based off of the assigned variant.

Usage Example

One possible example of when variants may be used is in a web application when there is a desire to test different visuals. In the following examples a mock of how one might assign different variants of a web page background to their users is shown.

//
// Modify view based off multiple possible variants
model.BackgroundUrl = featureVariantManager.GetVariantAsync<string>("HomeBackground", cancellationToken);

return View(model);

Configuring a Dynamic Feature

  • Assigner: The assigner that should be used to select which variant should be used any time this feature is accessed.
  • Variants: The different variants of the dynamic feature.
    • Default: Whether the variant should be used if no variant could be explicitly assigned.
    • Configuration Reference: A reference to the configuration of the variant to be used as typed options in the application.
    • Assignment Parameters: The parameters used in the assignment process to determine if this variant should be used.
{
  "FeatureManagement":
  {
    "ShoppingCart": {
      "Assigner": "Targeting",
      "Variants": [
        {
          "Default": true,
          "Name": "Big",
          "ConfigurationReference": "ShoppingCart:Big",
          "AssignmentParameters": {
            "Audience": {
              "Users": [
                "Alec",
                "Jeff",
                "Alicia"
              ]
            }
          }
        },
        {
          "Name": "Small",
          "ConfigurationReference": "ShoppingCart:Small",
          "AssignmentParameters": {
            "Audience": {
              "Users": [
                "Susan",
                "JohnDoe"
              ]
            }
          }
        }
      ]
  },
  "ShoppingCart": {
    "Big": {
      "Size": 400,
      "Color": "green"
    },
    "Small": {
      "Size": 150,
      "Color": "gray"
    }
  }
}

Assignment

One question that arises when working with variants is how to know which variant should be used at any given time. The act of choosing which variant should be used is called assignment. In this PR a built-in method of assignment is provided through the already established targeting paradigm. The pre-existing targeting feature filter used in feature flags allows certain segments of a user base to see a feature as on. Now there is a targeting assigner that can help assign different variants of a feature to different segments of an application's user base.

Custom Assignment

There may come a time when custom criteria is needed to decide which variant of a feature should be assigned when a feature is referenced. This is made possible by an extensibility model that allows the act of assignment to be overriden. Every feature registered in the feature management system that uses feature variants specifies what assigner should be used to choose a variant.

    public interface IFeatureVariantAssigner : IFeatureVariantAssignerMetadata
    {
        /// <summary>
        /// Assign a variant of a feature to be used based off of customized criteria.
        /// </summary>
        /// <param name="variantAssignmentContext">Information provided by the system to be used during the assignment process.</param>
        /// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
        /// <returns>The variant that should be assigned for a given feature.</returns>
        ValueTask<FeatureVariant> AssignVariantAsync(FeatureVariantAssignmentContext variantAssignmentContext, CancellationToken cancellationToken);
    }
}

Example implementation can be found in ~examples/CustomAssignmentConsoleApp/DayOfWeekAssigner.cs

@jimmyca15 jimmyca15 requested review from avanigupta and zhenlan July 28, 2021 00:14
@avanigupta
Copy link
Member

    private readonly ILogger _logger;

_logger not being used.


Refers to: src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs:22 in edb08e3. [](commit_id = edb08e3, deletion_comment = False)

@avanigupta
Copy link
Member

    private StringComparison ComparisonType => _options.IgnoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

ComparisonType not being used anywhere.


Refers to: src/Microsoft.FeatureManagement/Targeting/ContextualTargetingFilter.cs:35 in edb08e3. [](commit_id = edb08e3, deletion_comment = False)

@jimmyca15 jimmyca15 force-pushed the user/jimmyca/fVariants branch from 6c01a45 to 57b06b3 Compare August 30, 2021 21:54
if (filter is IFeatureFilter featureFilter && await featureFilter
.EvaluateAsync(context, cancellationToken)
.ConfigureAwait(false))
if (filter is IFeatureFilter featureFilter && await featureFilter.EvaluateAsync(context, cancellationToken).ConfigureAwait(false))
Copy link
Member

@zhenlan zhenlan Sep 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want to change this to the else clause of the if(useAppContext) too for consistency? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a behavioral change. Right now, since multiple filters can be registered for a feature, we allow contextual and non-contextual feature filters to be evaluated for a single feature flag evaluation.

Right now for assignment, since there's only one assigner that can run, we require a contextual assigner if app context is passed in.

Not sure if we want to unify these behaviors. But, if we do, I would opt to change the assignment requirement to allow a non-contextual assigner to run even if a context is passed in.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Looks like we didn't fail when a contextual filter is listed but without a context being provided today. This is a pseudo code I have in mind. Do you feel it's easier to understand the logic you explained above?

if (it's contextual filter)
{
    if (useAppContext)
    {
        do work for contextual filter
    }
    else
    {
        fail // we can skip this else block if we want to avoid the the breaking change.
    }    
}
else
{
    do work for non-contextual filter
}

I agree we should do the same for the assignment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think it makes it more clear that we expect a filter to be either contextual or noncontextual. Originally this was written with the design that a filter could be both, but we then added a check to ensure that a filter isn't used that implements both.

Given that we only expect a filter to be one of the two types, your layout is probably better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit contains a change that is somewhat between your suggestion and what already existed. It does clarify that at the root we distinguish non-contextual filter vs. contextual filter. However, we do check if app context is being used before trying to check if a filter is a contextual filter since the app context type is used in that process.

Copy link
Member Author

@jimmyca15 jimmyca15 Nov 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the change, the following conditions result in exceptions, whereas they did not before.

  • if feature references a contextual filter and caller passes a different app context type then the contextual filter supports.
  • if feature references a contextual filter and caller doesn't pass an app context

Updated error codes for consistency.
Copy link
Member

@zhenlan zhenlan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

Updated exception used for null child property.
Copy link
Member

@zhenlan zhenlan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's roll :)

@jimmyca15 jimmyca15 merged commit 45a6525 into microsoft:feature/v3 Nov 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants