Skip to main content

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

Azure Blob Storage  (feature-flags JSON file)
        |
        v
IFeatureFlagStorageService   (implemented by FeatureFlagFileStorageService in NuGetGallery.Core)
        |
        v
FeatureFlagCacheService      (background loop refreshes every 60 s by default)
        |
        v
FeatureFlagClient            (synchronous IsEnabled checks, reads from cache)
        |
        +---> IFeatureFlagService (NuGetGallery.Services — wraps client, defines named flag methods)
                |
                +---> NuGetGallery controllers, services, and background jobs
The storage layer is intentionally not part of this library — 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

FileClass / TypePurpose
Models/FeatureFlags.csFeatureFlagsImmutable snapshot of all flag state. Stores Features (name → FeatureStatus) and Flights (name → Flight). Both dictionaries are converted to OrdinalIgnoreCase at construction time.
Models/FeatureStatus.csFeatureStatusEnum with two values: Disabled = 0 and Enabled = 1. Represents the binary on/off state of a feature flag.
Models/Flight.csFlightImmutable value object describing a flight’s targeting rules: All, SiteAdmins, Accounts (list of usernames), and Domains (list of email domains).
FeatureFlagClient.csFeatureFlagClientConcrete 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.csFeatureFlagCacheServiceDrives 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.csFeatureFlagOptionsConfiguration POCO with a single property: RefreshInterval (default TimeSpan.FromMinutes(1)).
IFeatureFlagClient.csIFeatureFlagClientPublic 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.csIFeatureFlagCacheServiceCache-management interface. Exposes RunAsync, RefreshAsync, GetLatestFlagsOrNull, and GetRefreshTimeOrNull.
IFeatureFlagStorageService.csIFeatureFlagStorageServiceSingle-method extension point: Task<FeatureFlags> GetAsync(). The library owns the interface; callers supply the implementation.
IFeatureFlagTelemetryService.csIFeatureFlagTelemetryServiceOptional telemetry hook. Declares TrackFeatureFlagStaleness(TimeSpan). When not registered, FeatureFlagCacheService accepts a null reference and skips reporting.
IFlightUser.csIFlightUserUser identity contract for flight evaluation. Requires Username, EmailAddress, and IsSiteAdmin. Callers pass null for anonymous users.

Dependencies

NuGet Package References

PackagePurpose
Microsoft.Extensions.LoggingStructured 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.”
The IsEnabled(string flight, IFlightUser user, bool defaultValue) overload does not gate anonymous access automatically. Passing null for user and having Flight.All = true will return true for an anonymous caller. Code that should never enable a flight for logged-out users must check authentication state before calling this API — the library’s own documentation calls this out explicitly.
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.
The IFeatureFlagTelemetryService parameter in the longer FeatureFlagCacheService constructor is optional by convention — a separate constructor overload omits it and passes null internally. Callers that do not need staleness metrics can use the shorter constructor without registering a telemetry service.
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.