Real World Scenarios¶
This guide provides complete, production-ready examples for common real-world use cases.
E-Commerce Checkout Flow¶
Gradually roll out a new checkout experience.
public class CheckoutService
{
private readonly IFeatureFlagService _flagService;
private readonly ILogger<CheckoutService> _logger;
public CheckoutService(IFeatureFlagService flagService, ILogger<CheckoutService> logger)
{
_flagService = flagService;
_logger = logger;
}
public async Task<CheckoutResponse> ProcessCheckout(CheckoutRequest request)
{
// Determine which checkout flow to use
var useNewCheckout = await ShouldUseNewCheckout(request.UserId);
_logger.LogInformation(
"Processing checkout for user {UserId} using {CheckoutVersion}",
request.UserId,
useNewCheckout ? "new" : "legacy"
);
try
{
if (useNewCheckout)
{
return await ProcessNewCheckout(request);
}
else
{
return await ProcessLegacyCheckout(request);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Checkout failed for user {UserId}", request.UserId);
// Fallback to legacy checkout if new checkout fails
if (useNewCheckout)
{
_logger.LogWarning("Falling back to legacy checkout");
return await ProcessLegacyCheckout(request);
}
throw;
}
}
private async Task<bool> ShouldUseNewCheckout(string userId)
{
// Check if globally enabled
if (!await _flagService.IsFlagEnabledAsync("new-checkout-flow"))
return false;
// Get rollout percentage
var rolloutPercentage = await _flagService.GetValueAsync<int>("new-checkout-rollout", 0);
// Always use new checkout for internal users
var internalUsers = await _flagService.GetValueAsync<string>("internal-user-emails", "");
if (!string.IsNullOrEmpty(internalUsers))
{
var internalList = internalUsers.Split(',');
if (internalList.Any(email => userId.Contains(email)))
return true;
}
// Percentage-based rollout
var userHash = Math.Abs(userId.GetHashCode()) % 100;
return userHash < rolloutPercentage;
}
private async Task<CheckoutResponse> ProcessNewCheckout(CheckoutRequest request)
{
// New checkout with improved features
return new CheckoutResponse
{
Success = true,
OrderId = Guid.NewGuid().ToString(),
CheckoutVersion = "v2"
};
}
private async Task<CheckoutResponse> ProcessLegacyCheckout(CheckoutRequest request)
{
// Legacy checkout
return new CheckoutResponse
{
Success = true,
OrderId = Guid.NewGuid().ToString(),
CheckoutVersion = "v1"
};
}
}
public record CheckoutRequest(string UserId, List<CartItem> Items, PaymentInfo Payment);
public record CheckoutResponse(bool Success, string OrderId, string CheckoutVersion);
public record CartItem(string ProductId, int Quantity);
public record PaymentInfo(string Method, string Token);
Payment Gateway Switching¶
Switch payment gateways without code deployment.
public class PaymentGatewayService
{
private readonly IFeatureFlagService _flagService;
private readonly StripePaymentProvider _stripeProvider;
private readonly PayPalPaymentProvider _paypalProvider;
private readonly ILogger<PaymentGatewayService> _logger;
public PaymentGatewayService(
IFeatureFlagService flagService,
StripePaymentProvider stripeProvider,
PayPalPaymentProvider paypalProvider,
ILogger<PaymentGatewayService> logger)
{
_flagService = flagService;
_stripeProvider = stripeProvider;
_paypalProvider = paypalProvider;
_logger = logger;
}
public async Task<PaymentResult> ProcessPayment(decimal amount, string currency, string customerId)
{
// Get active payment gateway
var gateway = await _flagService.GetValueAsync<string>("payment-gateway", "stripe");
_logger.LogInformation(
"Processing payment of {Amount} {Currency} via {Gateway}",
amount, currency, gateway
);
// Check gateway-specific kill switch
var gatewayEnabled = await _flagService.IsFlagEnabledAsync($"payment-gateway-{gateway}");
if (!gatewayEnabled)
{
_logger.LogWarning("Payment gateway {Gateway} is disabled, falling back to default", gateway);
gateway = "stripe"; // Default fallback
}
try
{
return gateway.ToLowerInvariant() switch
{
"paypal" => await _paypalProvider.ProcessPayment(amount, currency, customerId),
"stripe" => await _stripeProvider.ProcessPayment(amount, currency, customerId),
_ => throw new InvalidOperationException($"Unknown payment gateway: {gateway}")
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Payment processing failed via {Gateway}", gateway);
// Try fallback gateway
if (gateway != "stripe")
{
_logger.LogInformation("Attempting fallback to Stripe");
return await _stripeProvider.ProcessPayment(amount, currency, customerId);
}
throw;
}
}
public async Task<Dictionary<string, bool>> GetGatewayHealth()
{
return new Dictionary<string, bool>
{
["stripe"] = await _flagService.IsFlagEnabledAsync("payment-gateway-stripe"),
["paypal"] = await _flagService.IsFlagEnabledAsync("payment-gateway-paypal")
};
}
}
// Example providers
public class StripePaymentProvider
{
public async Task<PaymentResult> ProcessPayment(decimal amount, string currency, string customerId)
{
// Stripe payment logic
return new PaymentResult(true, "stripe-transaction-id", "Stripe");
}
}
public class PayPalPaymentProvider
{
public async Task<PaymentResult> ProcessPayment(decimal amount, string currency, string customerId)
{
// PayPal payment logic
return new PaymentResult(true, "paypal-transaction-id", "PayPal");
}
}
public record PaymentResult(bool Success, string TransactionId, string Gateway);
UI Theme Management¶
Dynamic theme switching with user preferences.
public class ThemeService
{
private readonly IFeatureFlagService _flagService;
public ThemeService(IFeatureFlagService flagService)
{
_flagService = flagService;
}
public async Task<ThemeConfiguration> GetThemeForUser(string userId, string? userPreference = null)
{
// Check if theme customization is enabled
if (!await _flagService.IsFlagEnabledAsync("theme-customization"))
{
return ThemeConfiguration.Default;
}
// User preference takes priority
if (!string.IsNullOrEmpty(userPreference))
{
return GetThemeByName(userPreference);
}
// Check for A/B test variant
var abTestEnabled = await _flagService.IsFlagEnabledAsync("theme-ab-test");
if (abTestEnabled)
{
var variant = await GetABTestVariant(userId);
if (variant != null)
{
return variant;
}
}
// Default theme from flag
var defaultTheme = await _flagService.GetValueAsync<string>("default-theme", "light");
return GetThemeByName(defaultTheme);
}
private async Task<ThemeConfiguration?> GetABTestVariant(string userId)
{
var variantRollout = await _flagService.GetValueAsync<int>("theme-variant-rollout", 0);
if (variantRollout == 0)
return null;
var userHash = Math.Abs(userId.GetHashCode()) % 100;
if (userHash < variantRollout)
{
return ThemeConfiguration.DarkModern;
}
return null;
}
private ThemeConfiguration GetThemeByName(string name)
{
return name.ToLowerInvariant() switch
{
"dark" => ThemeConfiguration.Dark,
"dark-modern" => ThemeConfiguration.DarkModern,
"high-contrast" => ThemeConfiguration.HighContrast,
_ => ThemeConfiguration.Default
};
}
}
public record ThemeConfiguration(
string Name,
string PrimaryColor,
string BackgroundColor,
string TextColor)
{
public static ThemeConfiguration Default => new("light", "#007bff", "#ffffff", "#000000");
public static ThemeConfiguration Dark => new("dark", "#0d6efd", "#212529", "#ffffff");
public static ThemeConfiguration DarkModern => new("dark-modern", "#6366f1", "#0f172a", "#f8fafc");
public static ThemeConfiguration HighContrast => new("high-contrast", "#000000", "#ffffff", "#000000");
}
// API endpoint
app.MapGet("/api/theme", async (string userId, string? preference, ThemeService themeService) =>
{
var theme = await themeService.GetThemeForUser(userId, preference);
return Results.Ok(theme);
});
Rate Limiting with Feature Flags¶
Dynamic rate limit configuration.
public class RateLimitMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
public RateLimitMiddleware(RequestDelegate next, IMemoryCache cache)
{
_next = next;
_cache = cache;
}
public async Task InvokeAsync(HttpContext context, IFeatureFlagService flagService)
{
// Check if rate limiting is enabled
if (!await flagService.IsFlagEnabledAsync("enable-rate-limiting"))
{
await _next(context);
return;
}
var endpoint = context.GetEndpoint();
var rateLimitAttribute = endpoint?.Metadata.GetMetadata<RateLimitAttribute>();
if (rateLimitAttribute != null)
{
var clientId = GetClientIdentifier(context);
var limitKey = $"rate-limit:{rateLimitAttribute.Resource}:{clientId}";
// Get limit from feature flag (allows dynamic adjustment)
var limit = await flagService.GetValueAsync<int>(
$"rate-limit-{rateLimitAttribute.Resource}",
rateLimitAttribute.DefaultLimit
);
var windowMinutes = await flagService.GetValueAsync<int>(
$"rate-limit-window-{rateLimitAttribute.Resource}",
1
);
var requestCount = _cache.GetOrCreate(limitKey, entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(windowMinutes);
return 0;
});
if (requestCount >= limit)
{
context.Response.StatusCode = 429;
await context.Response.WriteAsJsonAsync(new
{
Error = "Rate limit exceeded",
Limit = limit,
Window = $"{windowMinutes} minutes"
});
return;
}
_cache.Set(limitKey, requestCount + 1);
}
await _next(context);
}
private string GetClientIdentifier(HttpContext context)
{
// Try to get user ID
var userId = context.User?.FindFirst("sub")?.Value;
if (!string.IsNullOrEmpty(userId))
return $"user:{userId}";
// Fallback to IP address
return $"ip:{context.Connection.RemoteIpAddress}";
}
}
[AttributeUsage(AttributeTargets.Method)]
public class RateLimitAttribute : Attribute
{
public string Resource { get; }
public int DefaultLimit { get; }
public RateLimitAttribute(string resource, int defaultLimit = 100)
{
Resource = resource;
DefaultLimit = defaultLimit;
}
}
// Usage
app.UseMiddleware<RateLimitMiddleware>();
app.MapGet("/api/search")
.WithMetadata(new RateLimitAttribute("search", 10));
Feature Deprecation Workflow¶
Gracefully deprecate features with warnings.
public class FeatureDeprecationMiddleware
{
private readonly RequestDelegate _next;
public FeatureDeprecationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context, IFeatureFlagService flagService)
{
var endpoint = context.GetEndpoint();
var deprecation = endpoint?.Metadata.GetMetadata<DeprecatedFeatureAttribute>();
if (deprecation != null)
{
var deprecationMode = await flagService.GetValueAsync<string>(
$"deprecation-{deprecation.FeatureKey}",
"warn"
);
switch (deprecationMode.ToLowerInvariant())
{
case "block":
context.Response.StatusCode = 410; // Gone
await context.Response.WriteAsJsonAsync(new
{
Error = "This feature has been deprecated and is no longer available",
DeprecatedSince = deprecation.DeprecatedSince,
Migration = deprecation.MigrationGuide
});
return;
case "warn":
context.Response.Headers.Add("X-Feature-Deprecated", "true");
context.Response.Headers.Add("X-Deprecation-Date", deprecation.DeprecatedSince.ToString("O"));
context.Response.Headers.Add("X-Migration-Guide", deprecation.MigrationGuide);
break;
case "silent":
// Allow without warning
break;
}
}
await _next(context);
}
}
[AttributeUsage(AttributeTargets.Method)]
public class DeprecatedFeatureAttribute : Attribute
{
public string FeatureKey { get; }
public DateTime DeprecatedSince { get; }
public string MigrationGuide { get; }
public DeprecatedFeatureAttribute(string featureKey, string deprecatedSince, string migrationGuide)
{
FeatureKey = featureKey;
DeprecatedSince = DateTime.Parse(deprecatedSince);
MigrationGuide = migrationGuide;
}
}
// Usage
app.MapGet("/api/v1/old-endpoint")
.WithMetadata(new DeprecatedFeatureAttribute(
"old-api-v1",
"2024-01-01",
"https://docs.example.com/migration/v2"
));
Canary Deployments¶
Use feature flags for canary deployments.
public class CanaryDeploymentService
{
private readonly IFeatureFlagService _flagService;
private readonly ILogger<CanaryDeploymentService> _logger;
public CanaryDeploymentService(
IFeatureFlagService flagService,
ILogger<CanaryDeploymentService> logger)
{
_flagService = flagService;
_logger = logger;
}
public async Task<bool> ShouldUseCanaryVersion(string userId, string feature)
{
// Check if canary deployment is active
if (!await _flagService.IsFlagEnabledAsync($"canary-{feature}"))
return false;
// Get canary rollout percentage
var canaryPercentage = await _flagService.GetValueAsync<int>(
$"canary-{feature}-percentage",
5 // Default 5%
);
// Check if user is in canary group
var userHash = Math.Abs(userId.GetHashCode()) % 100;
var isCanary = userHash < canaryPercentage;
if (isCanary)
{
_logger.LogInformation(
"User {UserId} selected for canary deployment of {Feature}",
userId, feature
);
}
return isCanary;
}
public async Task<ServiceResponse<T>> ExecuteWithCanary<T>(
string userId,
string feature,
Func<Task<T>> stableVersion,
Func<Task<T>> canaryVersion)
{
var useCanary = await ShouldUseCanaryVersion(userId, feature);
try
{
var result = useCanary
? await canaryVersion()
: await stableVersion();
return ServiceResponse<T>.Success(result, useCanary ? "canary" : "stable");
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in {Version} version of {Feature}",
useCanary ? "canary" : "stable", feature);
// Fallback to stable if canary fails
if (useCanary)
{
_logger.LogWarning("Falling back to stable version");
var fallbackResult = await stableVersion();
return ServiceResponse<T>.Success(fallbackResult, "stable-fallback");
}
throw;
}
}
}
public record ServiceResponse<T>(T Data, string Version, bool Success)
{
public static ServiceResponse<T> Success(T data, string version) =>
new(data, version, true);
}
// Usage
var response = await canaryService.ExecuteWithCanary(
userId,
"recommendation-engine",
stableVersion: async () => await GetStableRecommendations(userId),
canaryVersion: async () => await GetMLRecommendations(userId)
);
Next Steps¶
- Best Practices - Production best practices
- Testing - Testing feature flags
- Performance - Optimization tips