Skip to content

Conversation

@zhiyuanliang-ms
Copy link
Member

@zhiyuanliang-ms zhiyuanliang-ms commented Nov 22, 2023

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.

@zhiyuanliang-ms zhiyuanliang-ms marked this pull request as draft November 22, 2023 09:23
@zhiyuanliang-ms zhiyuanliang-ms changed the title Support app config server side schema In Progress: Support app config server side schema Nov 22, 2023
private readonly ConcurrentDictionary<string, FeatureDefinition> _definitions;
private IDisposable _changeSubscription;
private int _stale = 0;
private bool _serverSideSchemaEnabled;

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

Copy link
Member

@jimmyca15 jimmyca15 Nov 27, 2023

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.

Copy link

@RichardChen820 RichardChen820 Nov 29, 2023

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.

Copy link
Member

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.

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?

Copy link
Member

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();

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?

Copy link
Member Author

@zhiyuanliang-ms zhiyuanliang-ms Nov 23, 2023

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.

Copy link

@RichardChen820 RichardChen820 Nov 29, 2023

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.

@zhiyuanliang-ms zhiyuanliang-ms marked this pull request as ready for review November 24, 2023 06:09
@zhiyuanliang-ms zhiyuanliang-ms changed the title In Progress: Support app config server side schema Support app config server side schema Nov 24, 2023
@jimmyca15 jimmyca15 changed the title Support app config server side schema Support Azure App Configuration feature flag schema by default Nov 28, 2023
//
// 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]))

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?

Copy link
Member Author

@zhiyuanliang-ms zhiyuanliang-ms Nov 29, 2023

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.

Copy link
Member

Choose a reason for hiding this comment

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

{
enabledFor.Add(new FeatureFilterConfiguration()
{
Name = section[ConfigurationFields.LowercaseFeatureManagementSectionName],

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?

Copy link
Member Author

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.

@jimmyca15
Copy link
Member

PR description should reference the schema we are aiming to support:

https://github.com/Azure/AppConfiguration/blob/main/docs/FeatureManagement/FeatureFlag.v1.1.0.schema.json

featureManagementConfigurationSection = _configuration as IConfigurationSection;
}

IConfigurationSection featureFlagsConfigurationSection = featureManagementConfigurationSection.GetSection(AzureAppConfigurationFeatureFlagConfigurationFields.FeatureFlagsSectionName);
Copy link
Member

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.

Copy link
Member Author

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.


bool hasAzureAppConfigurationFeatureFlagSchema = HasAzureAppConfigurationFeatureFlagSchema(featureManagementConfigurationSection);

if (Interlocked.Exchange(ref _initialized, 1) != 1)
Copy link
Member

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.

//
// FeatureFlags section is an array
return string.IsNullOrEmpty(featureFlagsConfigurationSection.Value) &&
featureFlagsConfigurationSection.GetChildren()
Copy link
Member

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.

Copy link
Member Author

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()
Copy link
Member

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()
Copy link
Member

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)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private string GetFeatureFlagSectionName(IConfigurationSection section)
private string GetFeatureFlagName(IConfigurationSection section)

Copy link
Member

Choose a reason for hiding this comment

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

Or

Suggested change
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)

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?

Copy link

@RichardChen820 RichardChen820 Dec 18, 2023

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.

Copy link
Member

@jimmyca15 jimmyca15 Dec 18, 2023

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.

@mrm9084
Copy link
Contributor

mrm9084 commented Jan 5, 2024

@jimmyca15 @zhiyuanliang-ms

We want to augment the FeatureManagement-Dotnet to support the [Azure App Configuration feature flag schema] (https://github.com/Azure/AppConfiguration/blob/main/docs/FeatureManagement/FeatureFlag.v1.1.0.schema.json)
{
"FeatureManagement": {
"FeatureFlags": [
// App Configuration Feature Flag objects
]
}
}

Isn't this the "V2" schema. I thought we decided not to use it?

@zhiyuanliang-ms
Copy link
Member Author

@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.

@zhiyuanliang-ms zhiyuanliang-ms changed the title Support Azure App Configuration feature flag schema by default Support Microsoft Feature Flag schema feature flag schema by default Jan 23, 2024
@zhiyuanliang-ms
Copy link
Member Author

zhiyuanliang-ms commented Jan 23, 2024

@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.

@zhiyuanliang-ms zhiyuanliang-ms changed the title Support Microsoft Feature Flag schema feature flag schema by default Support Microsoft Feature Flag schema feature flag schema v1.1.0 by default Jan 23, 2024
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.

6 participants