Skip to content

Advanced Examples

This guide covers complex use cases and advanced patterns for using Flaggy in production environments.

Multi-Tenant Feature Flags

Manage features differently for each tenant.

public class MultiTenantFeatureService
{
    private readonly IFeatureFlagService _flagService;
    private readonly IHttpContextAccessor _contextAccessor;

    public MultiTenantFeatureService(
        IFeatureFlagService flagService, 
        IHttpContextAccessor contextAccessor)
    {
        _flagService = flagService;
        _contextAccessor = contextAccessor;
    }

    public async Task<bool> IsEnabledForCurrentTenant(string featureKey)
    {
        var tenantId = GetCurrentTenantId();
        return await IsEnabledForTenant(featureKey, tenantId);
    }

    public async Task<bool> IsEnabledForTenant(string featureKey, string tenantId)
    {
        // Priority 1: Check tenant-specific flag
        var tenantSpecificKey = $"{featureKey}:tenant:{tenantId}";
        var tenantFlag = await _flagService.GetFlagAsync(tenantSpecificKey);

        if (tenantFlag != null)
            return tenantFlag.IsEnabled;

        // Priority 2: Check tier-based flag
        var tier = await GetTenantTier(tenantId);
        var tierSpecificKey = $"{featureKey}:tier:{tier}";
        var tierFlag = await _flagService.GetFlagAsync(tierSpecificKey);

        if (tierFlag != null)
            return tierFlag.IsEnabled;

        // Priority 3: Check global flag
        return await _flagService.IsFlagEnabledAsync(featureKey);
    }

    private string GetCurrentTenantId()
    {
        return _contextAccessor.HttpContext?.Request.Headers["X-Tenant-Id"].FirstOrDefault() 
            ?? "default";
    }

    private async Task<string> GetTenantTier(string tenantId)
    {
        // In real scenario, fetch from database
        var tiers = new Dictionary<string, string>
        {
            ["tenant1"] = "enterprise",
            ["tenant2"] = "professional",
            ["tenant3"] = "basic"
        };

        return tiers.TryGetValue(tenantId, out var tier) ? tier : "basic";
    }
}

// Usage
app.MapGet("/api/features/analytics", async (MultiTenantFeatureService featureService) =>
{
    if (await featureService.IsEnabledForCurrentTenant("advanced-analytics"))
    {
        return Results.Ok(new { Feature = "Advanced Analytics", Enabled = true });
    }

    return Results.Forbid();
});

User Segmentation

Enable features for specific user groups.

public class UserSegmentationService
{
    private readonly IFeatureFlagService _flagService;

    public UserSegmentationService(IFeatureFlagService flagService)
    {
        _flagService = flagService;
    }

    public async Task<bool> IsEnabledForUser(string featureKey, User user)
    {
        // Check if feature is globally enabled
        if (!await _flagService.IsFlagEnabledAsync(featureKey))
            return false;

        // Get targeting rules from flag value
        var rulesJson = await _flagService.GetValueAsync<string>($"{featureKey}:rules");

        if (string.IsNullOrEmpty(rulesJson))
            return true; // No rules = enabled for everyone

        var rules = JsonSerializer.Deserialize<TargetingRules>(rulesJson);

        // Check user whitelist
        if (rules.WhitelistedUsers?.Contains(user.Email) == true)
            return true;

        // Check user attributes
        if (rules.RequiredAttributes != null)
        {
            foreach (var attr in rules.RequiredAttributes)
            {
                if (!user.Attributes.TryGetValue(attr.Key, out var value) || value != attr.Value)
                    return false;
            }
        }

        // Check percentage rollout
        if (rules.RolloutPercentage.HasValue)
        {
            var userHash = Math.Abs(user.Id.GetHashCode()) % 100;
            return userHash < rules.RolloutPercentage.Value;
        }

        return true;
    }
}

public record User(string Id, string Email, Dictionary<string, string> Attributes);

public record TargetingRules
{
    public List<string>? WhitelistedUsers { get; init; }
    public Dictionary<string, string>? RequiredAttributes { get; init; }
    public int? RolloutPercentage { get; init; }
}

// Setup targeting rules
await flagService.UpsertFlagAsync("beta-features", true, 
    value: JsonSerializer.Serialize(new TargetingRules
    {
        WhitelistedUsers = new List<string> { "admin@example.com", "beta-tester@example.com" },
        RequiredAttributes = new Dictionary<string, string> { ["country"] = "US" },
        RolloutPercentage = 10
    }));

Time-Based Flags

Enable features based on time windows.

public class TimeBasedFeatureService
{
    private readonly IFeatureFlagService _flagService;

    public TimeBasedFeatureService(IFeatureFlagService flagService)
    {
        _flagService = flagService;
    }

    public async Task<bool> IsEnabledAtTime(string featureKey, DateTime? checkTime = null)
    {
        if (!await _flagService.IsFlagEnabledAsync(featureKey))
            return false;

        var scheduleJson = await _flagService.GetValueAsync<string>($"{featureKey}:schedule");

        if (string.IsNullOrEmpty(scheduleJson))
            return true; // No schedule = always enabled

        var schedule = JsonSerializer.Deserialize<FeatureSchedule>(scheduleJson);
        var now = checkTime ?? DateTime.UtcNow;

        // Check if within active window
        if (schedule.StartTime.HasValue && now < schedule.StartTime.Value)
            return false;

        if (schedule.EndTime.HasValue && now > schedule.EndTime.Value)
            return false;

        // Check day of week restrictions
        if (schedule.AllowedDaysOfWeek?.Any() == true)
        {
            if (!schedule.AllowedDaysOfWeek.Contains(now.DayOfWeek))
                return false;
        }

        // Check time of day restrictions
        if (schedule.AllowedHours?.Any() == true)
        {
            if (!schedule.AllowedHours.Contains(now.Hour))
                return false;
        }

        return true;
    }
}

public record FeatureSchedule
{
    public DateTime? StartTime { get; init; }
    public DateTime? EndTime { get; init; }
    public List<DayOfWeek>? AllowedDaysOfWeek { get; init; }
    public List<int>? AllowedHours { get; init; }
}

// Setup time-based feature (Black Friday sale)
await flagService.UpsertFlagAsync("black-friday-sale", true,
    value: JsonSerializer.Serialize(new FeatureSchedule
    {
        StartTime = new DateTime(2024, 11, 29, 0, 0, 0, DateTimeKind.Utc),
        EndTime = new DateTime(2024, 12, 2, 23, 59, 59, DateTimeKind.Utc)
    }));

Feature Dependency Chains

Manage features that depend on other features.

public class FeatureDependencyService
{
    private readonly IFeatureFlagService _flagService;
    private readonly Dictionary<string, List<string>> _dependencies;

    public FeatureDependencyService(IFeatureFlagService flagService)
    {
        _flagService = flagService;
        _dependencies = new Dictionary<string, List<string>>
        {
            ["premium-analytics"] = new() { "basic-analytics", "data-export" },
            ["ai-recommendations"] = new() { "user-tracking", "ml-service" },
            ["advanced-reporting"] = new() { "basic-analytics", "premium-analytics" }
        };
    }

    public async Task<bool> IsEnabledWithDependencies(string featureKey)
    {
        // Check if feature itself is enabled
        if (!await _flagService.IsFlagEnabledAsync(featureKey))
            return false;

        // Check all dependencies
        if (_dependencies.TryGetValue(featureKey, out var deps))
        {
            foreach (var dependency in deps)
            {
                if (!await IsEnabledWithDependencies(dependency))
                {
                    return false;
                }
            }
        }

        return true;
    }

    public async Task<List<string>> GetMissingDependencies(string featureKey)
    {
        var missing = new List<string>();

        if (_dependencies.TryGetValue(featureKey, out var deps))
        {
            foreach (var dependency in deps)
            {
                if (!await _flagService.IsFlagEnabledAsync(dependency))
                {
                    missing.Add(dependency);
                }
            }
        }

        return missing;
    }
}

// Usage
app.MapGet("/api/features/premium-analytics", async (FeatureDependencyService depService) =>
{
    if (await depService.IsEnabledWithDependencies("premium-analytics"))
    {
        return Results.Ok(GetPremiumAnalytics());
    }

    var missing = await depService.GetMissingDependencies("premium-analytics");
    return Results.BadRequest(new 
    { 
        Error = "Feature dependencies not met", 
        MissingFeatures = missing 
    });
});

Integration with Middleware

Create reusable middleware for feature gating.

public class FeatureGateMiddleware
{
    private readonly RequestDelegate _next;

    public FeatureGateMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, IFeatureFlagService flagService)
    {
        var endpoint = context.GetEndpoint();
        var featureGate = endpoint?.Metadata.GetMetadata<FeatureGateAttribute>();

        if (featureGate != null)
        {
            if (!await flagService.IsFlagEnabledAsync(featureGate.FeatureKey))
            {
                context.Response.StatusCode = 404;
                await context.Response.WriteAsJsonAsync(new 
                { 
                    Error = "Feature not available" 
                });
                return;
            }
        }

        await _next(context);
    }
}

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class FeatureGateAttribute : Attribute
{
    public string FeatureKey { get; }

    public FeatureGateAttribute(string featureKey)
    {
        FeatureKey = featureKey;
    }
}

// Registration
app.UseMiddleware<FeatureGateMiddleware>();

// Usage
app.MapGet("/api/beta/feature")
   .WithMetadata(new FeatureGateAttribute("beta-api"))
   .WithName("BetaFeature");

Custom Caching Strategy

Implement a custom caching strategy with cache warming.

public class WarmCacheFeatureFlagService
{
    private readonly IFeatureFlagService _innerService;
    private readonly IMemoryCache _cache;
    private readonly ILogger<WarmCacheFeatureFlagService> _logger;

    public WarmCacheFeatureFlagService(
        IFeatureFlagService innerService,
        IMemoryCache cache,
        ILogger<WarmCacheFeatureFlagService> logger)
    {
        _innerService = innerService;
        _cache = cache;
        _logger = logger;
    }

    public async Task WarmCacheAsync()
    {
        _logger.LogInformation("Warming feature flag cache...");

        var flags = await _innerService.GetAllFlagsAsync();

        foreach (var flag in flags)
        {
            var cacheKey = $"flag:{flag.Key}";
            _cache.Set(cacheKey, flag, TimeSpan.FromMinutes(30));
        }

        _logger.LogInformation("Cache warmed with {Count} flags", flags.Count());
    }

    public async Task<bool> IsFlagEnabledAsync(string key)
    {
        var cacheKey = $"flag:{key}";

        if (_cache.TryGetValue(cacheKey, out FeatureFlag cachedFlag))
        {
            return cachedFlag.IsEnabled;
        }

        // Cache miss - fetch from service
        var flag = await _innerService.GetFlagAsync(key);

        if (flag != null)
        {
            _cache.Set(cacheKey, flag, TimeSpan.FromMinutes(30));
            return flag.IsEnabled;
        }

        return false;
    }
}

// Warm cache on startup
var warmService = app.Services.GetRequiredService<WarmCacheFeatureFlagService>();
await warmService.WarmCacheAsync();

Circuit Breaker Pattern

Combine feature flags with circuit breaker pattern.

public class CircuitBreakerFeatureService
{
    private readonly IFeatureFlagService _flagService;
    private readonly Dictionary<string, CircuitBreakerState> _states = new();

    public async Task<T> ExecuteWithCircuitBreaker<T>(
        string featureKey,
        Func<Task<T>> operation,
        Func<Task<T>> fallback)
    {
        // Check if feature is enabled
        if (!await _flagService.IsFlagEnabledAsync(featureKey))
        {
            return await fallback();
        }

        var state = GetOrCreateState(featureKey);

        // Check circuit breaker state
        if (state.IsOpen)
        {
            if (DateTime.UtcNow < state.OpenUntil)
            {
                return await fallback();
            }

            // Try to half-open
            state.IsOpen = false;
        }

        try
        {
            var result = await operation();
            state.FailureCount = 0; // Reset on success
            return result;
        }
        catch (Exception ex)
        {
            state.FailureCount++;

            // Open circuit if threshold exceeded
            if (state.FailureCount >= 5)
            {
                state.IsOpen = true;
                state.OpenUntil = DateTime.UtcNow.AddMinutes(5);
            }

            return await fallback();
        }
    }

    private CircuitBreakerState GetOrCreateState(string key)
    {
        if (!_states.TryGetValue(key, out var state))
        {
            state = new CircuitBreakerState();
            _states[key] = state;
        }
        return state;
    }

    private class CircuitBreakerState
    {
        public bool IsOpen { get; set; }
        public DateTime OpenUntil { get; set; }
        public int FailureCount { get; set; }
    }
}

// Usage
var result = await circuitBreaker.ExecuteWithCircuitBreaker(
    "external-api-integration",
    operation: async () => await CallExternalApi(),
    fallback: async () => await GetCachedData()
);

Next Steps