Overview
NuGet.Services.FeatureFlags is the core library for runtime feature control across NuGet.org services. It separates flag evaluation from flag persistence, providing a thin client that reads from an in-memory cache while a background refresh loop keeps the cache synchronized with the authoritative state stored externally (in practice, a JSON file in Azure Blob Storage). This means flag checks are always synchronous and allocation-free at call time — there are no async waits or I/O on the hot path.
The library distinguishes between two kinds of toggles. Features are binary on/off switches applicable to the entire system — for example, disabling package uploads when ingestion is degraded. Flights are targeted rollouts that can be enabled for a defined subset of users identified by username, email domain, or site-admin status, or for all users at once. This makes it possible to gradually expose new behavior to administrators or early-access testers without shipping separate code paths for each audience.
The project targets both net472 and netstandard2.0, keeping it compatible with the legacy ASP.NET MVC gallery and with modern .NET Standard-based jobs and validation services. Its only external dependency is Microsoft.Extensions.Logging, which it uses exclusively for diagnostic output when flags are missing or the cache has not yet been loaded.
Role in System
IFeatureFlagStorageService is the only extension point it exposes. The gallery implements FeatureFlagFileStorageService (read-only) and EditableFeatureFlagFileStorageService (adds ETag-based save and user-removal) in NuGetGallery.Core. Jobs that consume flags register their own storage implementations.
In-Memory Cache
All flag state is held in a single
FeatureFlags object stored in a private field on FeatureFlagCacheService. Reads are lock-free field reads; writes replace the field atomically. No per-request I/O.Targeted Flights
Flight supports four targeting axes: all users, site admins, a list of explicit usernames, and a list of email domains. All comparisons are case-insensitive. The All property short-circuits every other check when true.Graceful Degradation
When the cache is empty or a flag name is unknown,
IsEnabled returns the caller-supplied defaultValue rather than throwing, and emits a structured log entry at LogError or LogWarning level for observability.Staleness Telemetry
After each refresh attempt,
FeatureFlagCacheService reports the elapsed time since the last successful load to the optional IFeatureFlagTelemetryService. If flags have never loaded, it reports TimeSpan.MaxValue.Key Files and Classes
| File | Class / Type | Purpose |
|---|---|---|
Models/FeatureFlags.cs | FeatureFlags | Immutable snapshot of all flag state. Stores Features (name → FeatureStatus) and Flights (name → Flight). Both dictionaries are converted to OrdinalIgnoreCase at construction time. |
Models/FeatureStatus.cs | FeatureStatus | Enum with two values: Disabled = 0 and Enabled = 1. Represents the binary on/off state of a feature flag. |
Models/Flight.cs | Flight | Immutable value object describing a flight’s targeting rules: All, SiteAdmins, Accounts (list of usernames), and Domains (list of email domains). |
FeatureFlagClient.cs | FeatureFlagClient | Concrete IFeatureFlagClient. Reads the latest FeatureFlags snapshot from the cache and evaluates feature or flight membership synchronously. Logs a warning when a name is not found and an error when the cache is uninitialized. Uses MailAddress to parse the domain out of a user’s email address for domain-based flight targeting. |
FeatureFlagCacheService.cs | FeatureFlagCacheService | Drives the background refresh loop via RunAsync(CancellationToken) and exposes RefreshAsync() for an eager initial load at startup. Uses a factory Func<IFeatureFlagStorageService> so each refresh call can resolve a fresh storage instance. Reports staleness via IFeatureFlagTelemetryService after every attempt, including failed ones. |
FeatureFlagOptions.cs | FeatureFlagOptions | Configuration POCO with a single property: RefreshInterval (default TimeSpan.FromMinutes(1)). |
IFeatureFlagClient.cs | IFeatureFlagClient | Public client interface. Declares two IsEnabled overloads: one for features (no user context) and one for flights (accepts IFlightUser, which may be null for anonymous callers). |
IFeatureFlagCacheService.cs | IFeatureFlagCacheService | Cache-management interface. Exposes RunAsync, RefreshAsync, GetLatestFlagsOrNull, and GetRefreshTimeOrNull. |
IFeatureFlagStorageService.cs | IFeatureFlagStorageService | Single-method extension point: Task<FeatureFlags> GetAsync(). The library owns the interface; callers supply the implementation. |
IFeatureFlagTelemetryService.cs | IFeatureFlagTelemetryService | Optional telemetry hook. Declares TrackFeatureFlagStaleness(TimeSpan). When not registered, FeatureFlagCacheService accepts a null reference and skips reporting. |
IFlightUser.cs | IFlightUser | User identity contract for flight evaluation. Requires Username, EmailAddress, and IsSiteAdmin. Callers pass null for anonymous users. |
Dependencies
NuGet Package References
| Package | Purpose |
|---|---|
Microsoft.Extensions.Logging | Structured logging via ILogger<T>. Used by FeatureFlagClient and FeatureFlagCacheService to emit warnings and errors when flags are unavailable or unknown. |
Internal Project References
This project has no internal project references. It is consumed by other projects in the solution.Notable Patterns and Implementation Details
FeatureFlagCacheService accepts a Func<IFeatureFlagStorageService> factory rather than a direct IFeatureFlagStorageService instance. This allows the DI container (Autofac, in the gallery) to resolve a new storage instance on each refresh cycle — important when the storage service itself holds scoped resources such as HTTP clients or Azure SDK clients.RefreshAsync is intended to be called once at application startup before the background loop begins, ensuring that all flag checks after initialization have a valid cache. The interface’s XML doc explicitly calls this out: “This should be called at app startup to guarantee feature flags have been loaded.”All feature and flight name lookups use
OrdinalIgnoreCase dictionaries constructed at FeatureFlags creation time. This means the FeatureFlagClient performs a single dictionary lookup per call with no string allocation for case normalization.Feature flag state is serialized as a JSON file in Azure Blob Storage and managed through the NuGet.org admin panel. The
NuGetGallery.Core project provides EditableFeatureFlagFileStorageService, which adds ETag-based optimistic concurrency for saves and a RemoveUserAsync method for GDPR-style user data removal from flight allowlists.