Loading feature flags from new endpoint#738
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for loading feature flags from Azure App Configuration’s newer feature-flag endpoint and introduces an option to exclude “classic” feature flags stored under the .appconfig.featureflag/ key namespace.
Changes:
- Add opt-in behavior to exclude classic feature flags and prefer the new feature-flag endpoint as the source of truth.
- Introduce “new feature-flag selector/watcher” markers and update selector equality/hash behavior accordingly.
- Implement loading + change detection for the new feature-flag endpoint, including synthesis into the existing feature-management JSON schema.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Models/KeyValueWatcher.cs | Adds a flag to distinguish watchers targeting the new feature-flag endpoint. |
| src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Models/KeyValueSelector.cs | Adds a flag to distinguish selectors for the new endpoint and updates equality/hash logic. |
| src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureFlagSettingConverter.cs | New converter that synthesizes ConfigurationSetting instances from SDK FeatureFlag models using the existing feature-management JSON schema. |
| src/Microsoft.Extensions.Configuration.AzureAppConfiguration/FeatureManagement/FeatureFlagOptions.cs | Adds a new public option to exclude classic feature flags. |
| src/Microsoft.Extensions.Configuration.AzureAppConfiguration/Extensions/ConfigurationClientExtensions.cs | Adds change detection for the new feature-flag endpoint by comparing page ETags. |
| src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationProvider.cs | Routes loading/refresh logic between classic key-values and the new feature-flag endpoint; ensures new flags win on key conflicts. |
| src/Microsoft.Extensions.Configuration.AzureAppConfiguration/AzureAppConfigurationOptions.cs | Updates UseFeatureFlags to register new-endpoint selectors/watchers (and optionally omit classic flags); changes default SDK service version. |
| // The new endpoint uses null to mean "any name". A bare "*" is equivalent. | ||
| if (string.IsNullOrEmpty(nameFilter) || nameFilter == "*") | ||
| { | ||
| nameFilter = null; | ||
| } |
| private static ConfigurationClientOptions GetDefaultClientOptions() | ||
| { | ||
| var clientOptions = new ConfigurationClientOptions(ConfigurationClientOptions.ServiceVersion.V2023_11_01); | ||
| var clientOptions = new ConfigurationClientOptions(ConfigurationClientOptions.ServiceVersion.V2026_05_01_Preview); | ||
| clientOptions.Retry.MaxRetries = MaxRetries; |
| /// <summary> | ||
| /// When set to true, classic feature flags (key-values whose key is prefixed with | ||
| /// ".appconfig.featureflag/") are not loaded from the configuration store. Only feature flags | ||
| /// returned by the new feature-flag endpoint will be loaded. | ||
| /// Defaults to false; classic flags are loaded alongside new flags for backward compatibility. | ||
| /// </summary> | ||
| public bool ExcludeClassicFeatureFlags { get; set; } = false; |
|
|
||
| foreach (SdkFeatureFlag ff in page.Values) | ||
| { | ||
| ConfigurationSetting synthesized = FeatureFlagSettingConverter.ToConfigurationSetting(ff); |
There was a problem hiding this comment.
The new feature flags shouldn't be converted to configuration setting. When we load a new feature flag, we should break it down into it's associated key-value pairs, like the feature flag adapter does for classic feature flags, and inject that into the configuration directly.
There was a problem hiding this comment.
I saw some updates in the PR. Is this one still being worked on?
There was a problem hiding this comment.
Yes, working in progess
There was a problem hiding this comment.
Updated, new feature flags now map to the internal model (provider FeatureFlag) and share the ff adapter's logic.
|
This needs to go into the preview branch. |
708b5b1 to
56f906e
Compare
| /// configured <see cref="ClientOptions"/> so that the feature-flag client communicates with the | ||
| /// same store, audience, transport and retry behavior as the configuration client. | ||
| /// </summary> | ||
| internal FeatureFlagClientOptions GetFeatureFlagClientOptions() |
There was a problem hiding this comment.
If we're going to separate the options, I think it will be better to have FeatureFlagClientOptions as a property just like ClientOptions.
And whenever ConfigureClientOptions is used, we can reflect the changes on FeatureFlagClientOptions.
|
|
||
| namespace Microsoft.Extensions.Configuration.AzureAppConfiguration | ||
| { | ||
| internal class ClientWrapper |
There was a problem hiding this comment.
How about we introduce IAppConfigurationClient which exposes the methods that we need to use from both FeatureFlagClient and ConfigurationClient. And the implementation AppConfigurationClient can contain both internally.
There was a problem hiding this comment.
That way the provider can still work with a single client.
| // Emit configuration for feature flags loaded from the standalone feature-flag endpoint. | ||
| // These are mapped directly from the SDK model to feature-management key-values without | ||
| // round-tripping through a synthesized ConfigurationSetting. | ||
| if (sdkFFs != null && sdkFFs.Count > 0) |
There was a problem hiding this comment.
I think that there should be a separate method to add feature flags into data.
private void ProcessFeatureFlags(
Dictionary<string, ConfigurationSetting> data,
IEnumerable<FeatureFlag> featureFlags);
| if (key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) | ||
| SdkFeatureFlag featureFlag = kvp.Value; | ||
|
|
||
| FeatureManagement.FeatureFlag model = SdkFeatureFlagModelMapper.MapToModel(featureFlag); |
There was a problem hiding this comment.
There shouldn't be an intermediate state. We should go straight from FeatureFlag to the IEnumerable<KeyValuePair<string, string>> that should go into configuration.
| using Azure; | ||
| using Azure.Data.AppConfiguration; | ||
| using Microsoft.Extensions.Configuration.AzureAppConfiguration.Models; | ||
| using SdkFeatureFlag = Azure.Data.AppConfiguration.FeatureFlag; |
There was a problem hiding this comment.
Let's avoid introducing this concept of 'SdkFeatureFlag'.
Going forward 'FeatureFlag' refers to the new flags.
All previous naming for old flags should be renamed to ClassicFeatureFlag. Including the type here.
There was a problem hiding this comment.
If we do need some separation, we can use the terms classic feature flag and standalone feature flag.
| return data; | ||
| } | ||
|
|
||
| private async Task LoadFeatureFlags( |
There was a problem hiding this comment.
I'd like to keep classic feature flags and standalone feature flags separate.
Previously, feature flags were loaded only as classic key-values prefixed with
.appconfig.featureflag/. This PR registers a parallel "new feature flag" selector/watcher for each FeatureFlagSelector, fetches flags via the dedicated SDK endpoint, and synthesizes them back into the feature-management JSON schema so downstream parsing is unchanged. Classic flags can be turned off via a new opt-in flagExcludeClassicFeatureFlags.Changes: