Что такое фича-флаги и зачем они вам нужны
Представьте: вы написали крутую новую фичу — например, умный поиск с автодополнением. Всё протестировано, код прошёл ревью. Но вы не уверены на 100%, что пользователи её примут. Или боитесь, что под нагрузкой она ляжет. Что делать?
Раньше ответ был прост: «Залей в прод и молись». Сегодня мы умнее. Используем фича-флаги (feature flags, feature toggles) — механизм, который позволяет включать и выключать функциональность на лету, без перекомпиляции и повторного деплоя.
В этой статье на PythonLib мы разберём, как внедрить фича-флаги в C# проектах — от простейшей реализации до продвинутых сценариев с A/B тестированием и канареечными релизами.
Почему фича-флаги — это мастхэв для C# разработчика
Feature flags C# решают три ключевые задачи:
- Безопасный деплой: заливайте код в прод, даже если фича ещё не готова. Просто выключите флаг.
- Мгновенное отключение: что-то пошло не так? Один клик в админке — и проблемная фича исчезла.
- Гибкое управление функциями: включайте фичи для конкретных пользователей, регионов или по времени.
И самое приятное: для управления функциями в .NET не нужно изобретать велосипед — есть готовая библиотека Microsoft.FeatureManagement.
Самый простой способ: конфигурационный файл
Начнём с базового подхода — через appsettings.json. Это идеально для простых feature toggles .NET.
Шаг 1. Добавляем настройки
{
"FeatureManagement": {
"NewSearchEngine": true,
"DarkMode": false,
"BetaDashboard": true
}
}
Шаг 2. Регистрируем сервис
В Program.cs добавляем:
builder.Services.AddFeatureManagement();
Шаг 3. Используем в коде
public class SearchController
{
private readonly IFeatureManager _featureManager;
public SearchController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
public async Task<IActionResult> Search(string query)
{
if (await _featureManager.IsEnabledAsync("NewSearchEngine"))
{
return View("NewSearch", await _newEngine.SearchAsync(query));
}
return View("LegacySearch", await _oldEngine.SearchAsync(query));
}
}
Готово! Теперь, чтобы включить новую фичу, достаточно поменять true на false в конфиге и перезапустить приложение. Но мы же хотим без перезапуска?
Динамические фича-флаги: переключение без рестарта
Для conditional features C# без перезагрузки используем провайдеры конфигурации, которые подгружают изменения. Лучший вариант — база данных или Redis.
Создаём провайдер из базы данных
public class DatabaseFeatureDefinitionProvider : IFeatureDefinitionProvider
{
private readonly AppDbContext _db;
public DatabaseFeatureDefinitionProvider(AppDbContext db)
{
_db = db;
}
public async Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
{
var feature = await _db.FeatureFlags
.FirstOrDefaultAsync(f => f.Name == featureName);
return feature != null
? new FeatureDefinition { Name = feature.Name, EnabledFor = feature.EnabledFor }
: null;
}
public IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
{
return _db.FeatureFlags
.Select(f => new FeatureDefinition { Name = f.Name, EnabledFor = f.EnabledFor })
.AsAsyncEnumerable();
}
}
Регистрируем:
builder.Services.AddFeatureManagement()
.AddFeatureDefinitionProvider<DatabaseFeatureDefinitionProvider>();
Теперь изменения в БД применяются мгновенно — идеально для soft launch C#.
Продвинутые сценарии: фильтры и A/B тестирование
Библиотека Microsoft.FeatureManagement поддерживает мощные фильтры. Это открывает дорогу для A/B тестирования C# и канареечных релизов.
Процентное включение
{
"FeatureManagement": {
"NewCheckout": {
"EnabledFor": [
{
"Name": "Microsoft.Percentage",
"Parameters": {
"Value": 25
}
}
]
}
}
}
25% пользователей увидят новый интерфейс оформления заказа. Остальные — старый.
Включение по времени
{
"FeatureManagement": {
"HolidayTheme": {
"EnabledFor": [
{
"Name": "Microsoft.TimeWindow",
"Parameters": {
"Start": "2024-12-01T00:00:00Z",
"End": "2025-01-10T23:59:59Z"
}
}
]
}
}
}
Новогодняя тема включится автоматически 1 декабря и отключится 10 января. Никаких ручных действий.
Кастомный фильтр для конкретного пользователя
Создаём свой фильтр для canary release C#:
[FilterAlias("UserGroup")]
public class UserGroupFilter : IFeatureFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
public UserGroupFilter(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
{
var user = _httpContextAccessor.HttpContext.User;
var settings = context.Parameters.Get<UserGroupFilterSettings>();
return Task.FromResult(
user.IsInRole(settings.GroupName) ||
user.HasClaim("beta-tester", "true")
);
}
}
public class UserGroupFilterSettings
{
public string GroupName { get; set; }
}
Использование:
{
"FeatureManagement": {
"PremiumFeature": {
"EnabledFor": [
{
"Name": "UserGroup",
"Parameters": {
"GroupName": "PremiumUsers"
}
}
]
}
}
}
Когда фича-флаги — это плохая идея
Честно: не всё так радужно. Тогглы функций .NET имеют тёмную сторону. Вот когда их лучше не использовать:
- Слишком много флагов: код превращается в спагетти из if-else. Договоритесь о лимите — 10-15 активных флагов на проект.
- Вечные флаги: если фича включена у 100% пользователей месяц — пора удалить старый код и флаг. Иначе получите технический долг.
- Временные костыли: фича-флаг не должен заменять нормальное тестирование или рефакторинг.