Skip to content

IFeatureFlagProvider Interface Reference

Overview

IFeatureFlagProvider is the abstraction layer for feature flag data persistence. It defines the low-level interface that different storage backends implement (MySQL, PostgreSQL, In-Memory, etc.). Most applications use this indirectly through IFeatureFlagService, but it can be accessed directly for advanced use cases.

Namespace: Flaggy.Abstractions

Note: Unless you're implementing a custom provider, use IFeatureFlagService instead, which adds automatic caching and convenience methods.

Core Methods

GetFlagAsync

Retrieves a single feature flag from the provider.

Task<FeatureFlag?> GetFlagAsync(string key, CancellationToken cancellationToken = default);

Parameters: - key (string): The unique identifier for the feature flag - cancellationToken (CancellationToken, optional): Token to cancel the async operation

Returns: Task<FeatureFlag?> - The FeatureFlag object or null if not found

Example:

var provider = serviceProvider.GetRequiredService<IFeatureFlagProvider>();

var flag = await provider.GetFlagAsync("feature-key");
if (flag != null)
{
    Console.WriteLine($"Flag: {flag.Key}, Enabled: {flag.is_enabled}");
}

Use Cases: - Direct provider access in custom implementations - Bypassing cache for immediate data checks (when necessary) - Integration with external systems


GetAllFlagsAsync

Retrieves all feature flags from the provider.

Task<IEnumerable<FeatureFlag>> GetAllFlagsAsync(CancellationToken cancellationToken = default);

Parameters: - cancellationToken (CancellationToken, optional): Token to cancel the async operation

Returns: Task<IEnumerable<FeatureFlag>> - Collection of all FeatureFlag objects

Example:

var provider = serviceProvider.GetRequiredService<IFeatureFlagProvider>();

var allFlags = await provider.GetAllFlagsAsync();
foreach (var flag in allFlags)
{
    Console.WriteLine($"Key: {flag.Key}, Enabled: {flag.is_enabled}");
}

Use Cases: - Cache initialization - Bulk export operations - Verification of all stored flags


CRUD Operations

CreateFlagAsync

Creates a new feature flag in the storage backend.

Task<bool> CreateFlagAsync(FeatureFlag flag, CancellationToken cancellationToken = default);

Parameters: - flag (FeatureFlag): The feature flag to create - cancellationToken (CancellationToken, optional): Token to cancel the async operation

Returns: Task<bool> - True if created successfully, false otherwise

Example:

var provider = serviceProvider.GetRequiredService<IFeatureFlagProvider>();

var newFlag = new FeatureFlag
{
    Key = "beta-feature",
    is_enabled = false,
    description = "Beta feature for testing",
    Value = null
};

bool created = await provider.CreateFlagAsync(newFlag);

Implementation Notes: - Should return false if a flag with the same key already exists - created_at/updated_at timestamps should be set by the provider - Each provider implementation may have different behavior for duplicate keys


UpdateFlagAsync

Updates an existing feature flag in the storage backend.

Task<bool> UpdateFlagAsync(FeatureFlag flag, CancellationToken cancellationToken = default);

Parameters: - flag (FeatureFlag): The feature flag with updated values - cancellationToken (CancellationToken, optional): Token to cancel the async operation

Returns: Task<bool> - True if updated successfully, false if flag not found

Example:

var provider = serviceProvider.GetRequiredService<IFeatureFlagProvider>();

var flag = await provider.GetFlagAsync("beta-feature");
if (flag != null)
{
    flag.is_enabled = true;
    flag.Value = "production-ready";

    bool updated = await provider.UpdateFlagAsync(flag);
}

Implementation Notes: - updated_at timestamp should be updated by the provider - Should preserve created_at timestamp - Return false if the flag key doesn't exist


DeleteFlagAsync

Deletes a feature flag from the storage backend.

Task<bool> DeleteFlagAsync(string key, CancellationToken cancellationToken = default);

Parameters: - key (string): The unique identifier of the flag to delete - cancellationToken (CancellationToken, optional): Token to cancel the async operation

Returns: Task<bool> - True if deleted successfully, false if flag not found

Example:

var provider = serviceProvider.GetRequiredService<IFeatureFlagProvider>();

bool deleted = await provider.DeleteFlagAsync("deprecated-feature");
if (deleted)
{
    Console.WriteLine("Flag deleted successfully");
}

Use Cases: - Removing deprecated features - Cleanup during migrations - Archive management


Built-in Provider Implementations

Flaggy includes several provider implementations:

InMemoryFeatureFlagProvider

Stores flags in application memory. Useful for testing and development.

services.AddFlaggy(options =>
{
    options.Provider = new InMemoryFeatureFlagProvider();
});

Characteristics: - No persistence across application restarts - Fast in-memory access - Suitable for unit tests and prototyping


MySqlFeatureFlagProvider

Stores flags in a MySQL database.

services.AddFlaggy(options =>
{
    options.UseMySQL(
        connectionString: "Server=localhost;Database=flaggy;User=root;Password=password"
    );
});

Characteristics: - Persistent storage - Shared across multiple instances - Requires database setup and migrations


PostgreSQL Provider

Stores flags in a PostgreSQL database.

services.AddFlaggy(options =>
{
    options.UsePostgreSQL(
        connectionString: "Host=localhost;Database=flaggy;username=user;Password=password"
    );
});

Characteristics: - Persistent storage - Shared across multiple instances - Enterprise-grade database support


Custom Provider Implementation

To implement a custom provider, create a class implementing IFeatureFlagProvider:

using Flaggy.Abstractions;
using Flaggy.Models;

public class CustomFeatureFlagProvider : IFeatureFlagProvider
{
    public async Task<FeatureFlag?> GetFlagAsync(string key, CancellationToken cancellationToken = default)
    {
        // Retrieve flag from custom storage
        return await FetchFromCustomStorage(key, cancellationToken);
    }

    public async Task<IEnumerable<FeatureFlag>> GetAllFlagsAsync(CancellationToken cancellationToken = default)
    {
        // Retrieve all flags from custom storage
        return await FetchAllFromCustomStorage(cancellationToken);
    }

    public async Task<bool> CreateFlagAsync(FeatureFlag flag, CancellationToken cancellationToken = default)
    {
        // Save new flag to custom storage
        return await SaveToCustomStorage(flag, cancellationToken);
    }

    public async Task<bool> UpdateFlagAsync(FeatureFlag flag, CancellationToken cancellationToken = default)
    {
        // Update flag in custom storage
        return await UpdateInCustomStorage(flag, cancellationToken);
    }

    public async Task<bool> DeleteFlagAsync(string key, CancellationToken cancellationToken = default)
    {
        // Delete flag from custom storage
        return await DeleteFromCustomStorage(key, cancellationToken);
    }

    // Custom implementation methods
    private Task<FeatureFlag?> FetchFromCustomStorage(string key, CancellationToken ct) => throw new NotImplementedException();
    private Task<IEnumerable<FeatureFlag>> FetchAllFromCustomStorage(CancellationToken ct) => throw new NotImplementedException();
    private Task<bool> SaveToCustomStorage(FeatureFlag flag, CancellationToken ct) => throw new NotImplementedException();
    private Task<bool> UpdateInCustomStorage(FeatureFlag flag, CancellationToken ct) => throw new NotImplementedException();
    private Task<bool> DeleteFromCustomStorage(string key, CancellationToken ct) => throw new NotImplementedException();
}

Register your custom provider:

services.AddFlaggy(options =>
{
    options.Provider = new CustomFeatureFlagProvider();
});


Provider vs Service

When to Use IFeatureFlagProvider Directly

  • Implementing a custom provider
  • Direct database access for advanced operations
  • Bypassing cache intentionally
  • Standard feature flag checks
  • CRUD operations in applications
  • Leveraging automatic caching and cache invalidation
  • Using convenience extension methods

Example Comparison:

// Using Provider (direct access, no caching)
var provider = serviceProvider.GetRequiredService<IFeatureFlagProvider>();
var flag = await provider.GetFlagAsync("feature");

// Using Service (cached, with extensions)
var service = serviceProvider.GetRequiredService<IFeatureFlagService>();
var enabled = await service.is_enabledAsync("feature");
bool toggled = await service.ToggleFlagAsync("feature");


Performance Considerations

  • GetAllFlagsAsync: Loads all flags. Suitable for initialization but avoid calling repeatedly without caching
  • GetFlagAsync: Direct flag lookup. Should be fast for most providers
  • Create/Update/Delete: Trigger cache invalidation in the service layer
  • Cancellation tokens: Always respect cancellation tokens for long-running operations