-
Notifications
You must be signed in to change notification settings - Fork 119
Support Microsoft Feature Flag schema feature flag schema v1.1.0 by default #319
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
Support Microsoft Feature Flag schema feature flag schema v1.1.0 by default #319
Conversation
| private readonly ConcurrentDictionary<string, FeatureDefinition> _definitions; | ||
| private IDisposable _changeSubscription; | ||
| private int _stale = 0; | ||
| private bool _serverSideSchemaEnabled; |
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.
Is _FeatureFlagArraySchemaEnabled better? Anyway,we'd avoid using the “serverSide” word, since it is not meaningful for FeatureManagement library
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.
There is no such thing as server side schema. This library exists completely independently from Azure App Configuration. All notion of server side should be eliminated. Instead it should be referred to as the Azure App Configuration feature flag schema.
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.
The current PR title makes sense to me, this PR is exactly for that purpose, support App Config Schema. But I think we should not name anything with "app configuration" in the code. This library should not have any strong correlation with App configuration, it's independent.
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.
"App Configuration" is the branding. It's like the FM library adopts a schema that is created by App Configuration.
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.
So you are suggesting naming correlated variables with "AppConfiguration" in the code?
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.
We are augmenting the library to support the "Azure App Configuration feature flag schema". In that sense there is a depenency.
|
|
||
| if (_serverSideSchemaEnabled) | ||
| { | ||
| return featureFlagsConfigurationSection.GetChildren(); |
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.
Once FeatureFlags:[] section exists, we no longer treat other root configurations as feature flags, is this intended?
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. Besides, FeatureFlags:{} and FeatureFlags:[] will be treated as the same thing by IConfiguration.
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'm trying to imagine such a user case below in Kubernetes (since App Config Kubernetes Provider would be the only place that provides the server-side schema).
A customer has a configuration file that includes FeatureManagement section (old schema) in his app. He uses Kubernetes Provider to get some feature flags from Azure App Configuration and expect the feature flags from App Config can overwrite the feature flags in his local file if duplicated, but keep the local feature flags if the App Config end doesn't have them.
I'm not sure whether this is a valid user case we should support. But apparently, we can't handle this case with the current code.
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
| // | ||
| // Arrays in json such as "myKey": [ "some", "values" ] | ||
| // Are accessed through the configuration system by using the array index as the property name, e.g. "myKey": { "0": "some", "1": "values" } | ||
| if (int.TryParse(section.Key, out int _) && !string.IsNullOrEmpty(section[ConfigurationFields.LowercaseFeatureManagementSectionName])) |
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.
Should we throw an exception if the child of client_filters is not an array?
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 think we should just ignore it if we cannot parse it.
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.
Schema validation should be based on the schema
https://github.com/Azure/AppConfiguration/blob/main/docs/FeatureManagement/FeatureFlag.v1.1.0.schema.json
| { | ||
| enabledFor.Add(new FeatureFilterConfiguration() | ||
| { | ||
| Name = section[ConfigurationFields.LowercaseFeatureManagementSectionName], |
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.
Should we handle the situation that the name and parameters are missing?
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.
If the name is missing, it will not enter the if branch. The parameters is ok to be missing.
|
PR description should reference the schema we are aiming to support: |
src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
| featureManagementConfigurationSection = _configuration as IConfigurationSection; | ||
| } | ||
|
|
||
| IConfigurationSection featureFlagsConfigurationSection = featureManagementConfigurationSection.GetSection(AzureAppConfigurationFeatureFlagConfigurationFields.FeatureFlagsSectionName); |
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.
This is assigned to with an as implying this can be null. Looks like a possible null-ref.
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.
Sorry, I didn't understand. _configuration cannot be null, otherwise an exception will be thrown in the constructor.
IConfiguration can always be casted to IConfigurationSection. I think there could not be a null-ref.
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
|
|
||
| bool hasAzureAppConfigurationFeatureFlagSchema = HasAzureAppConfigurationFeatureFlagSchema(featureManagementConfigurationSection); | ||
|
|
||
| if (Interlocked.Exchange(ref _initialized, 1) != 1) |
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.
Interlocked usage here can allow a simultaneously executing thread to bypass the critical section and return from the method without initializing. With the requirement to initialize here a lock cannot be avoided. Single usage of lock on initialization should be acceptable.
src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs
Outdated
Show resolved
Hide resolved
| // | ||
| // FeatureFlags section is an array | ||
| return string.IsNullOrEmpty(featureFlagsConfigurationSection.Value) && | ||
| featureFlagsConfigurationSection.GetChildren() |
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.
If featureFlagsConfigurationSection.GetChildren() returns empty, then All would return true. Seems unintended.
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.
This is intended. If there is "FeatureFlags" section, either it is empty or an array, we consider it as using the App Config schema.
|
|
||
| private static bool HasAzureAppConfigurationFeatureFlagSchema(IConfiguration featureManagementConfiguration) | ||
| { | ||
| bool hasFeatureFlagsSection = featureManagementConfiguration.GetChildren() |
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.
A few suggestions in this section. Get the section while checking for it's existence. Check if feature flags section has any children while doing an all check.
IConfigurationSection featureFlagsConfigurationSection = featureManagementConfiguration
.GetChildren()
.FirstOrDefault(section =>
string.Equals(
section.Key,
AzureAppConfigurationFeatureFlagFields.FeatureFlagsSectionName,
StringComparison.OrdinalIgnoreCase));
if (featureFlagsConfigurationSection != null)
{
if (!string.IsNullOrEmpty(featureFlagsConfigurationSection.Value))
{
return false;
}
featureFlagChildren = featureFlagsConfigurationSection.GetChildren();
return featureFlagChildren.Any() && featureFlagChildren.All(section => int.TryParse(section.Key, out int _));
}
return false;
| return Enumerable.Empty<IConfigurationSection>(); | ||
| } | ||
|
|
||
| bool hasFeatureManagementSection = _configuration.GetChildren() |
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.
Better to get the section with FirstOrDefault, rather than just check for it's existence, else you will just need to get it again later.
| }; | ||
| } | ||
|
|
||
| private string GetFeatureFlagSectionName(IConfigurationSection section) |
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.
| private string GetFeatureFlagSectionName(IConfigurationSection section) | |
| private string GetFeatureFlagName(IConfigurationSection section) |
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.
Or
| private string GetFeatureFlagSectionName(IConfigurationSection section) | |
| private string GetFeatureName(IConfigurationSection section) |
Given the caller uses a variable named featureName
|
|
||
| lock (_lock) | ||
| { | ||
| if (Interlocked.Read(ref _initialized) == 0) |
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.
lock can sure only one thread can enter this block, what is the reason for using Interlocked.Read(ref _initialized) to atomically get the value?
Mark int32 type _initialized as volatile then judge _initialized == 0 is fine. Am I missing anything?
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.
Or use
if (Interlocked.CompareExchange(ref _initialized, 1, 0))==0 {
_azureAppConfigurationFeatureFlagSchemaEnabled = hasAzureAppConfigurationFeatureFlagSchema; }
is also fine.
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.
Interlocked exchange does perform an atomic swap, but it also has the added benefit of forcing a read from memory. Here, interlocked avoids cached read from CPU. Marking the variable as volatile will have lasting effect throughout the lifetime of the application whereas interlocked usage in the init check only happens during initialization.
Isn't this the "V2" schema. I thought we decided not to use it? |
|
@mrm9084 This is not the V2 schema because this PR is targeting on the main branch. The variant and telemetry part are implemented in the preview/release v4 branch which is for the ongoing preview release. We didn't plan the App Config feature flag schema support for the preview release. |
|
@zhenlan @jimmyca15 @RichardChen820 As discussed in the email: "Microsoft Feature Flag schema should be the name we use going forward", I made some update to this PR. |
We want to augment the FeatureManagement-Dotnet to support the Microsoft Feature Flag schema v1.1.0
{ "FeatureManagement": { "FeatureFlags": [ // Microsoft Feature Flag objects ] } }The Microsoft Feature Flag definitions will be listed in the "FeatureFlags" array.
It is not allowed to use different schemas at the same time. If we can find the “FeatureFlags” array in the “FeatureManagement” section, we will only parse the Microsoft feature flag definitions listed in the "FeatureFlags" array. The old schema (if any) will be ignored.