Migration Guide¶
Overview¶
This comprehensive guide helps teams migrate from other feature flag libraries to Flaggy. Whether you're coming from LaunchDarkly, Unleash, FeatureToggle, or custom implementations, this guide provides step-by-step instructions, code examples, and troubleshooting tips.
Why Migrate to Flaggy?¶
Key Advantages¶
- Simple and Lightweight: No complex infrastructure or external services required
- Multiple Storage Options: Choose from InMemory, MySQL, PostgreSQL, or MS SQL Server
- Built-in Dashboard: Manage flags through a beautiful web UI
- Zero Dependencies: Self-hosted solution with no vendor lock-in
- Cost-Effective: No per-seat or usage-based pricing
- Full Control: Your data stays in your infrastructure
When to Migrate¶
Consider migrating to Flaggy when:
- You want to reduce third-party service costs
- Your current solution is over-complicated for your needs
- You need full control over your feature flag infrastructure
- You want a self-hosted solution without external dependencies
- Your team needs a simpler, more maintainable approach
Pre-Migration Checklist¶
Before starting the migration, complete these preparatory steps:
1. Inventory Your Flags¶
// Document all existing flags
// Flag Name | Status | Dependencies | Owner | Purpose
// new-checkout | Enabled | Payment API | Team-A | New checkout flow
// dark-mode | Enabled | UI components | Team-B | Theme switcher
// beta-features | Disabled | Multiple | Team-C | Beta program
2. Identify Flag Dependencies¶
Map out which parts of your application depend on each flag:
// Example dependency mapping
/*
Flag: new-checkout
- Controllers: CheckoutController.cs
- Services: PaymentService.cs, OrderService.cs
- Views: Checkout.cshtml
- Tests: CheckoutTests.cs
*/
3. Plan Rollback Strategy¶
Ensure you have a rollback plan in case issues arise:
// Keep old library temporarily
// Use feature flag to switch between old and new implementations
if (await _flagService.IsEnabledAsync("use-flaggy-library"))
{
// Use Flaggy
isEnabled = await _flaggyService.IsEnabledAsync("feature-x");
}
else
{
// Use old library as fallback
isEnabled = await _oldService.IsEnabledAsync("feature-x");
}
4. Set Up Testing Environment¶
Create a test environment to validate the migration:
# Clone production database to test environment
# Install Flaggy in test environment
# Validate flag behavior matches production
Migration from LaunchDarkly¶
LaunchDarkly is a popular feature flag SaaS platform. Here's how to migrate to Flaggy.
Step 1: Export LaunchDarkly Flags¶
Export your flags using LaunchDarkly's API or dashboard:
// Using LaunchDarkly API to export flags
public async Task<List<LaunchDarklyFlag>> ExportLaunchDarklyFlags()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "api-key-xxx");
var response = await client.GetAsync("https://app.launchdarkly.com/api/v2/flags/your-project");
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<LaunchDarklyFlag>>(json);
}
Step 2: Install Flaggy¶
dotnet add package Flaggy
dotnet add package Flaggy.Provider.MySQL
Step 3: Configure Flaggy¶
using Flaggy.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Configure Flaggy with your preferred provider
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(
connectionString: builder.Configuration.GetConnectionString("DefaultConnection"),
tableName: "feature_flags", // optional
userTableName: "users", // optional
autoMigrate: true // optional
);
options.UseMemoryCache(TimeSpan.FromMinutes(5));
});
var app = builder.Build();
app.Run();
Step 4: Transform and Import Flags¶
public async Task MigrateFromLaunchDarkly(IFeatureFlagService flaggyService)
{
var ldFlags = await ExportLaunchDarklyFlags();
foreach (var ldFlag in ldFlags)
{
// Map LaunchDarkly flag to Flaggy
await flaggyService.CreateFlagAsync(new FeatureFlag
{
Key = ldFlag.Key,
IsEnabled = ldFlag.On, // LaunchDarkly uses 'on' property
Value = ldFlag.Variations?.FirstOrDefault()?.Value?.ToString(),
Description = $"{ldFlag.Name} - {ldFlag.Description}"
});
}
}
Step 5: Update Code References¶
// Before: LaunchDarkly
using LaunchDarkly.Sdk.Server;
public class OrderController : ControllerBase
{
private readonly ILdClient _ldClient;
public async Task<IActionResult> Process()
{
var user = LaunchDarkly.Sdk.User.WithKey(_currentUser.Id);
var isEnabled = await _ldClient.BoolVariationAsync("new-checkout", user, false);
return isEnabled ? NewCheckout() : OldCheckout();
}
}
// After: Flaggy
using Flaggy.Abstractions;
public class OrderController : ControllerBase
{
private readonly IFeatureFlagService _flagService;
public async Task<IActionResult> Process()
{
var isEnabled = await _flagService.IsEnabledAsync("new-checkout");
return isEnabled ? NewCheckout() : OldCheckout();
}
}
Step 6: Handle User Targeting (Optional)¶
LaunchDarkly supports user targeting. In Flaggy, you can implement this with flag values:
// LaunchDarkly targeting logic
// Before:
var user = LaunchDarkly.Sdk.User.WithKey(userId).AndEmail(email);
var isEnabled = await _ldClient.BoolVariationAsync("beta-feature", user, false);
// After: Flaggy with custom targeting
var betaUsers = await _flagService.GetValueAsync<string>("beta-users", "");
var betaUserList = betaUsers.Split(',');
var isEnabled = betaUserList.Contains(userId) ||
await _flagService.IsEnabledAsync("beta-feature");
Migration Checklist¶
- Export all flags from LaunchDarkly
- Install Flaggy packages
- Configure Flaggy services
- Import flags to Flaggy
- Update code references from
ILdClienttoIFeatureFlagService - Test all flag evaluations
- Update CI/CD pipelines
- Remove LaunchDarkly SDK
- Cancel LaunchDarkly subscription
Migration from Unleash¶
Unleash is an open-source feature toggle system. Here's the migration path.
Step 1: Export Unleash Flags¶
public async Task<List<UnleashFeature>> ExportUnleashFlags()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "unleash-api-key");
var response = await client.GetAsync("https://your-unleash-instance.com/api/admin/features");
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<List<UnleashFeature>>(json);
}
Step 2: Install and Configure Flaggy¶
using Flaggy.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Flaggy with PostgreSQL (similar to Unleash)
builder.Services.AddFlaggy(options =>
{
options.UsePostgreSQL(
connectionString: builder.Configuration.GetConnectionString("DefaultConnection"),
tableName: "feature_flags", // optional
userTableName: "users", // optional
autoMigrate: true // optional
);
});
var app = builder.Build();
app.Run();
Step 3: Migrate Flags¶
public async Task MigrateFromUnleash(IFeatureFlagService flaggyService)
{
var unleashFlags = await ExportUnleashFlags();
foreach (var unleashFlag in unleashFlags)
{
await flaggyService.CreateFlagAsync(new FeatureFlag
{
Key = unleashFlag.Name,
IsEnabled = unleashFlag.Enabled,
Value = unleashFlag.Variants?.FirstOrDefault()?.Name,
Description = unleashFlag.Description
});
}
}
Step 4: Update Code¶
// Before: Unleash
using Unleash;
public class FeatureService
{
private readonly IUnleash _unleash;
public bool IsFeatureEnabled(string feature)
{
return _unleash.IsEnabled(feature);
}
}
// After: Flaggy
using Flaggy.Abstractions;
public class FeatureService
{
private readonly IFeatureFlagService _flagService;
public async Task<bool> IsFeatureEnabled(string feature)
{
return await _flagService.IsEnabledAsync(feature);
}
}
Migration Checklist¶
- Export flags from Unleash
- Install Flaggy packages
- Configure Flaggy with PostgreSQL provider
- Import flags to Flaggy
- Update code from
IUnleashtoIFeatureFlagService - Test flag evaluations
- Migrate activation strategies (if applicable)
- Update deployment scripts
- Decommission Unleash instance
Migration from FeatureToggle Library¶
If you're using the FeatureToggle library for .NET, here's how to migrate.
Step 1: Identify Current Toggles¶
// Before: FeatureToggle
using FeatureToggle;
public class MyFeature : SimpleFeatureToggle { }
public class MyController : ControllerBase
{
private readonly MyFeature _myFeature;
public IActionResult Index()
{
if (_myFeature.FeatureEnabled)
{
return NewFeature();
}
return OldFeature();
}
}
Step 2: Install Flaggy¶
dotnet add package Flaggy
dotnet add package Flaggy.Provider.MySQL
Step 3: Configure Flaggy¶
using Flaggy.Extensions;
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(
connectionString: builder.Configuration.GetConnectionString("DefaultConnection")
);
});
Step 4: Update Code¶
// After: Flaggy
using Flaggy.Abstractions;
public class MyController : ControllerBase
{
private readonly IFeatureFlagService _flagService;
public async Task<IActionResult> Index()
{
if (await _flagService.IsEnabledAsync("my-feature"))
{
return NewFeature();
}
return OldFeature();
}
}
Step 5: Seed Initial Flags¶
// Create flags based on your FeatureToggle configuration
var app = builder.Build();
await app.SeedFeatureFlagsAsync(
new FeatureFlag { Key = "my-feature", IsEnabled = true },
new FeatureFlag { Key = "beta-feature", IsEnabled = false },
new FeatureFlag { Key = "new-ui", IsEnabled = true }
);
app.Run();
Migration Checklist¶
- Document all FeatureToggle classes
- Install Flaggy packages
- Configure Flaggy services
- Create corresponding flags in Flaggy
- Replace toggle classes with Flaggy service calls
- Update dependency injection
- Test all toggle points
- Remove FeatureToggle library
Migration from Custom Implementation¶
Many teams have custom feature flag implementations. Here's how to migrate.
Common Custom Patterns¶
Pattern 1: Configuration-Based Flags¶
// Before: appsettings.json
{
"FeatureFlags": {
"NewCheckout": true,
"BetaMode": false
}
}
// Code
public class FeatureFlagService
{
private readonly IConfiguration _config;
public bool IsEnabled(string key)
{
return _config.GetValue<bool>($"FeatureFlags:{key}");
}
}
// After: Flaggy
using Flaggy.Abstractions;
public class FeatureFlagService
{
private readonly IFeatureFlagService _flagService;
public async Task<bool> IsEnabled(string key)
{
return await _flagService.IsEnabledAsync(key);
}
}
Pattern 2: Database-Based Flags¶
// Before: Custom database implementation
public class CustomFlagService
{
private readonly DbContext _db;
public async Task<bool> IsEnabled(string key)
{
var flag = await _db.Flags.FindAsync(key);
return flag?.IsEnabled ?? false;
}
}
// After: Flaggy (handles database operations internally)
using Flaggy.Abstractions;
public class FeatureFlagService
{
private readonly IFeatureFlagService _flagService;
public async Task<bool> IsEnabled(string key)
{
return await _flagService.IsEnabledAsync(key);
}
}
Pattern 3: Environment Variable Flags¶
// Before: Environment variables
public class FeatureFlagService
{
public bool IsEnabled(string key)
{
var value = Environment.GetEnvironmentVariable($"FEATURE_{key}");
return bool.TryParse(value, out var result) && result;
}
}
// After: Flaggy with environment-based initialization
var app = builder.Build();
// Seed from environment variables at startup
await app.InitializeFeatureFlagsAsync(async flagService =>
{
await SeedFromEnvironmentVariables(flagService);
});
public async Task SeedFromEnvironmentVariables(IFeatureFlagService flagService)
{
var envVars = Environment.GetEnvironmentVariables();
foreach (DictionaryEntry env in envVars)
{
var key = env.Key.ToString();
if (key.StartsWith("FEATURE_"))
{
var flagKey = key.Replace("FEATURE_", "").ToLower().Replace("_", "-");
var isEnabled = bool.TryParse(env.Value?.ToString(), out var result) && result;
await flagService.UpsertFlagAsync(flagKey, isEnabled);
}
}
}
Migration Strategy¶
Step 1: Analyze Current Implementation¶
Document your current implementation:
// Document each flag
/*
Flag: new-checkout
Storage: appsettings.json
Default: false
Used In: CheckoutController.cs, OrderService.cs
Owner: team-payments
*/
Step 2: Create Migration Script¶
public class FlagMigrationService
{
private readonly IConfiguration _config;
private readonly IFeatureFlagService _flagService;
public async Task MigrateCustomFlags()
{
// From configuration
var configFlags = _config.GetSection("FeatureFlags")
.GetChildren()
.ToDictionary(x => x.Key, x => x.Get<bool>());
foreach (var flag in configFlags)
{
await _flagService.CreateFlagAsync(new FeatureFlag
{
Key = ConvertToKebabCase(flag.Key),
IsEnabled = flag.Value,
Description = $"Migrated from appsettings.json"
});
}
// From database
var dbFlags = await _db.CustomFlags.ToListAsync();
foreach (var flag in dbFlags)
{
await _flagService.CreateFlagAsync(new FeatureFlag
{
Key = flag.Name,
IsEnabled = flag.Active,
Value = flag.ConfigValue,
Description = flag.Notes
});
}
}
private string ConvertToKebabCase(string input)
{
return Regex.Replace(input, "(?<!^)([A-Z])", "-$1").ToLower();
}
}
Step 3: Implement Adapter Pattern (Temporary)¶
During migration, use an adapter to support both systems:
public class FeatureFlagAdapter
{
private readonly IFeatureFlagService _flaggy;
private readonly IConfiguration _config;
private readonly bool _useFlaggy;
public FeatureFlagAdapter(
IFeatureFlagService flaggy,
IConfiguration config)
{
_flaggy = flaggy;
_config = config;
_useFlaggy = config.GetValue<bool>("UseFlaggy", false);
}
public async Task<bool> IsEnabled(string key)
{
if (_useFlaggy)
{
return await _flaggy.IsEnabledAsync(key);
}
// Fallback to old implementation
return _config.GetValue<bool>($"FeatureFlags:{key}", false);
}
}
Step 4: Update Code Gradually¶
// Phase 1: Replace with adapter
services.AddScoped<FeatureFlagAdapter>();
// Phase 2: Test thoroughly
// Phase 3: Switch to Flaggy directly
services.AddFlaggy(options =>
{
options.UseMySQL(connectionString: connectionString);
});
// Phase 4: Remove old implementation
Migration Checklist¶
- Document current custom implementation
- Create migration script for flag data
- Install Flaggy packages
- Implement adapter pattern for gradual migration
- Migrate flags to Flaggy
- Update code references gradually
- Test each migration phase
- Remove adapter and old code
- Clean up old flag storage (config, database tables, etc.)
Post-Migration Tasks¶
After completing the migration, perform these tasks:
1. Verify All Flags¶
public async Task VerifyMigration(IFeatureFlagService flagService)
{
var allFlags = await flagService.GetAllFlagsAsync();
Console.WriteLine($"Total flags migrated: {allFlags.Count()}");
foreach (var flag in allFlags)
{
Console.WriteLine($"✓ {flag.Key}: {flag.IsEnabled}");
}
}
2. Update Documentation¶
# Feature Flags Documentation
## Flaggy Configuration
- **Provider**: MySQL
- **Cache**: Memory Cache (5 min TTL)
- **Dashboard**: https://app.company.com/flaggy
## Flag List
| Flag Key | Status | Purpose | Owner |
|----------|--------|---------|-------|
| new-checkout | Enabled | New checkout flow | team-payments |
| dark-mode | Enabled | Theme switcher | team-ui |
3. Train Team Members¶
# Flaggy Quick Start Guide
## Checking a Flag
```csharp
var isEnabled = await _flagService.IsEnabledAsync("feature-key");
Creating a Flag¶
await _flagService.CreateFlagAsync(new FeatureFlag
{
Key = "my-feature",
IsEnabled = true,
Description = "Description here"
});
Dashboard Access¶
Visit: https://app.company.com/flaggy
### 4. Set Up Monitoring
```csharp
// Add health checks
builder.Services.AddHealthChecks()
.AddCheck<FeatureFlagHealthCheck>("feature_flags");
// Log flag operations
public class MonitoredFeatureFlagService : IFeatureFlagService
{
private readonly IFeatureFlagService _inner;
private readonly ILogger _logger;
public async Task<bool> IsEnabledAsync(string key, CancellationToken ct = default)
{
var result = await _inner.IsEnabledAsync(key, ct);
_logger.LogInformation("Flag {Key} evaluated: {Result}", key, result);
return result;
}
}
5. Clean Up Old Dependencies¶
# Remove old feature flag packages
dotnet remove package LaunchDarkly.ServerSdk
# or
dotnet remove package Unleash.Client
# or
dotnet remove package FeatureToggle
# Update project files
# Remove old configuration sections
# Delete migration adapter code
Troubleshooting¶
Issue: Flags Not Evaluating Correctly¶
Symptoms: Flags return unexpected values after migration
Solutions:
// 1. Verify flag was created correctly
var flag = await _flagService.GetFlagAsync("feature-key");
Console.WriteLine($"Key: {flag?.Key}, Enabled: {flag?.IsEnabled}");
// 2. Check for naming mismatches
// Old: "NewCheckout"
// New: "new-checkout" (kebab-case)
// 3. Clear cache if stale
await _flagService.RefreshCacheAsync();
Issue: Performance Degradation¶
Symptoms: Application slower after migration
Solutions:
// 1. Enable caching
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(connectionString: connectionString);
options.UseMemoryCache(TimeSpan.FromMinutes(5)); // Increase cache time
});
// 2. Use distributed cache for multi-server
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(connectionString: connectionString);
options.UseRedisCache("localhost:6379", TimeSpan.FromMinutes(5));
});
// 3. Check flag usage patterns
// Avoid checking flags in tight loops
Issue: Database Connection Errors¶
Symptoms: Cannot connect to database provider
Solutions:
// 1. Verify connection string
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
Console.WriteLine($"Connection: {connectionString}");
// 2. Test database connectivity
using (var connection = new MySqlConnection(connectionString))
{
await connection.OpenAsync();
Console.WriteLine("Database connected successfully");
}
// 3. Check auto-migration status
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(
connectionString: connectionString,
autoMigrate: true // Ensure tables are created
);
});
Issue: Missing Flags After Migration¶
Symptoms: Some flags from old system are missing
Solutions:
// 1. Verify migration script ran completely
public async Task VerifyAllFlagsMigrated(
List<string> oldFlagKeys,
IFeatureFlagService flagService)
{
var newFlags = await flagService.GetAllFlagsAsync();
var newFlagKeys = newFlags.Select(f => f.Key).ToHashSet();
var missing = oldFlagKeys.Where(k => !newFlagKeys.Contains(k)).ToList();
if (missing.Any())
{
Console.WriteLine($"Missing flags: {string.Join(", ", missing)}");
// Re-run migration for missing flags
}
}
// 2. Check for naming transformation errors
// 3. Manually create missing flags
Migration Timeline Example¶
Week 1: Preparation¶
- Day 1-2: Document all existing flags
- Day 3: Set up test environment with Flaggy
- Day 4-5: Create and test migration scripts
Week 2: Implementation¶
- Day 1-2: Migrate flags to test environment
- Day 3-4: Update code in test branches
- Day 5: Integration testing
Week 3: Deployment¶
- Day 1: Deploy to staging environment
- Day 2-3: Staging validation
- Day 4: Production deployment (with rollback plan)
- Day 5: Monitor and validate
Week 4: Cleanup¶
- Day 1-2: Remove old library code
- Day 3: Update documentation
- Day 4: Train team
- Day 5: Retrospective and lessons learned
Getting Help¶
If you encounter issues during migration:
- Check Documentation: Review Flaggy docs
- GitHub Issues: Search or create an issue
- Community Support: Ask in discussions
- Professional Support: Contact for enterprise migration assistance
Next Steps¶
After migrating to Flaggy:
- Deployment Guide - Deploy Flaggy to production
- Security Best Practices - Secure your feature flags
- Performance Optimization - Optimize for high traffic
- Best Practices - Follow recommended patterns