Basic Examples¶
This guide provides simple, straightforward examples of common feature flag scenarios using Flaggy.
Simple Feature Toggle¶
The most basic use case - enable or disable a feature.
app.MapGet("/api/products", async (IFeatureFlagService flagService) =>
{
// Check if new UI is enabled
if (await flagService.IsFlagEnabledAsync("new-product-ui"))
{
return Results.Ok(GetNewProductUI());
}
return Results.Ok(GetLegacyProductUI());
});
A/B Testing¶
Test two different versions of a feature with different users.
public class ProductController : ControllerBase
{
private readonly IFeatureFlagService _flagService;
public ProductController(IFeatureFlagService flagService)
{
_flagService = flagService;
}
[HttpGet("api/products/{id}")]
public async Task<IActionResult> GetProduct(int id)
{
// Get A/B test variant
var variant = await _flagService.GetValueAsync<string>("product-page-variant", "A");
return variant switch
{
"B" => Ok(GetProductPageVariantB(id)),
_ => Ok(GetProductPageVariantA(id))
};
}
private object GetProductPageVariantA(int id) => new { Version = "A", ProductId = id };
private object GetProductPageVariantB(int id) => new { Version = "B", ProductId = id, ExtraFeature = true };
}
Gradual Rollout¶
Roll out a feature to a percentage of users.
public class FeatureRolloutService
{
private readonly IFeatureFlagService _flagService;
public FeatureRolloutService(IFeatureFlagService flagService)
{
_flagService = flagService;
}
public async Task<bool> IsNewCheckoutEnabledForUser(string userId)
{
// Get rollout percentage (0-100)
var rolloutPercentage = await _flagService.GetValueAsync<int>("new-checkout-rollout", 0);
if (rolloutPercentage == 0)
return false;
if (rolloutPercentage >= 100)
return true;
// Use user ID hash to determine if user is in rollout group
var userHash = Math.Abs(userId.GetHashCode()) % 100;
return userHash < rolloutPercentage;
}
}
// Usage in controller
app.MapGet("/checkout", async (string userId, FeatureRolloutService rolloutService) =>
{
if (await rolloutService.IsNewCheckoutEnabledForUser(userId))
{
return Results.Ok("New Checkout");
}
return Results.Ok("Legacy Checkout");
});
Kill Switch¶
Quickly disable a feature in production without redeployment.
public class PaymentService
{
private readonly IFeatureFlagService _flagService;
private readonly ILogger<PaymentService> _logger;
public PaymentService(IFeatureFlagService flagService, ILogger<PaymentService> logger)
{
_flagService = flagService;
_logger = logger;
}
public async Task<PaymentResult> ProcessPayment(PaymentRequest request)
{
// Check if payment processing is enabled (kill switch)
if (!await _flagService.IsFlagEnabledAsync("enable-payment-processing"))
{
_logger.LogWarning("Payment processing is disabled via kill switch");
return PaymentResult.Disabled("Payment processing is temporarily disabled");
}
// Process payment
return await ProcessPaymentInternal(request);
}
private async Task<PaymentResult> ProcessPaymentInternal(PaymentRequest request)
{
// Actual payment logic
return PaymentResult.Success();
}
}
public record PaymentRequest(string CardNumber, decimal Amount);
public record PaymentResult(bool IsSuccess, string Message)
{
public static PaymentResult Success() => new(true, "Payment successful");
public static PaymentResult Disabled(string message) => new(false, message);
}
Maintenance Mode¶
Put your application in maintenance mode.
// Middleware
public class MaintenanceModeMiddleware
{
private readonly RequestDelegate _next;
private readonly IFeatureFlagService _flagService;
public MaintenanceModeMiddleware(RequestDelegate next, IFeatureFlagService flagService)
{
_next = next;
_flagService = flagService;
}
public async Task InvokeAsync(HttpContext context)
{
// Skip maintenance check for admin paths
if (context.Request.Path.StartsWithSegments("/admin"))
{
await _next(context);
return;
}
// Check maintenance mode flag
if (await _flagService.IsFlagEnabledAsync("maintenance-mode"))
{
var message = await _flagService.GetValueAsync<string>("maintenance-message",
"System is under maintenance. Please try again later.");
context.Response.StatusCode = 503;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(new { error = message });
return;
}
await _next(context);
}
}
// Registration
app.UseMiddleware<MaintenanceModeMiddleware>();
Dark Launches¶
Deploy features to production but keep them hidden from users.
public class DashboardController : ControllerBase
{
private readonly IFeatureFlagService _flagService;
[HttpGet("api/dashboard")]
public async Task<IActionResult> GetDashboard()
{
var dashboard = new
{
Charts = GetBasicCharts(),
Reports = GetBasicReports()
};
// Dark launch: new analytics feature is deployed but not visible
if (await _flagService.IsFlagEnabledAsync("new-analytics"))
{
dashboard = dashboard with
{
Analytics = GetNewAnalytics()
};
}
// Dark launch: AI recommendations deployed but disabled
if (await _flagService.IsFlagEnabledAsync("ai-recommendations"))
{
dashboard = dashboard with
{
Recommendations = GetAIRecommendations()
};
}
return Ok(dashboard);
}
private object GetBasicCharts() => new { Type = "Basic" };
private object GetBasicReports() => new { Type = "Basic" };
private object GetNewAnalytics() => new { Type = "Advanced", Features = new[] { "Real-time", "Predictions" } };
private object GetAIRecommendations() => new { Type = "AI", Model = "GPT-4" };
}
Configuration Management¶
Manage application configuration dynamically.
public class ConfigurationService
{
private readonly IFeatureFlagService _flagService;
public ConfigurationService(IFeatureFlagService flagService)
{
_flagService = flagService;
}
public async Task<int> GetMaxUploadSizeAsync()
{
// Default: 10MB
return await _flagService.GetValueAsync<int>("max-upload-size-mb", 10);
}
public async Task<string> GetApiEndpointAsync()
{
return await _flagService.GetValueAsync<string>("api-endpoint",
"https://api.example.com");
}
public async Task<bool> IsFeatureEnabledForTenant(string tenantId, string featureKey)
{
// Check tenant-specific flag
var tenantFlag = $"{featureKey}-{tenantId}";
var isTenantEnabled = await _flagService.IsFlagEnabledAsync(tenantFlag);
if (isTenantEnabled)
return true;
// Fallback to global flag
return await _flagService.IsFlagEnabledAsync(featureKey);
}
}
// Usage
app.MapPost("/upload", async (IFormFile file, ConfigurationService config) =>
{
var maxSize = await config.GetMaxUploadSizeAsync();
var maxBytes = maxSize * 1024 * 1024;
if (file.Length > maxBytes)
{
return Results.BadRequest($"File size exceeds maximum of {maxSize}MB");
}
// Process upload
return Results.Ok("Upload successful");
});
Seeding Initial Flags¶
Create default flags at application startup.
var builder = WebApplication.CreateBuilder();
builder.Services.AddFlaggy(options =>
{
options.UsePostgreSQL(
connectionString: "Host=localhost;..."
);
});
var app = builder.Build();
// Seed default flags
await app.SeedFeatureFlagsAsync(
new FeatureFlag
{
Key = "new-product-ui",
is_enabled = false,
description = "New product page UI"
},
new FeatureFlag
{
Key = "maintenance-mode",
is_enabled = false,
description = "Put application in maintenance mode"
},
new FeatureFlag
{
Key = "enable-payment-processing",
is_enabled = true,
description = "Enable payment processing (kill switch)"
},
new FeatureFlag
{
Key = "max-upload-size-mb",
is_enabled = true,
Value = "10",
description = "Maximum upload size in megabytes"
}
);
app.Run();
Next Steps¶
- Advanced Examples - Complex scenarios
- Real World Scenarios - Production use cases
- API Reference - Complete API documentation