Irina Korneva 28 сентября 2023

🏴 Фича-флаги в .NET: от простого к сложному

В этой статье мы обсудим, что такое фича-флаги, и разберём работу с ними.
🏴 Фича-флаги в .NET: от простого к сложному
Данная статья является переводом. Ссылка на оригинал.

Что такое фича-флаг?

Фича-флаг — это способ включить или выключить фичу или ветвь кода внутри приложения.

В идеале, «переключатель» не должен влиять на код или требовать новый релиз. Это просто тумблер, который вы включаете или выключаете с помощью какой-либо конфигурации.

Такие флаги имеют несколько вариаций:

  • Условие на основе окружения, которое включает фичу во время QA и выключает в продакшне, пока фича не будет готова;
  • Фича, которую необходимо развернуть для определённого пользователя или группы пользователей, например тестировщиков;
  • Основанная на времени фича, которую необходимо развернуть в будущем или на определённое время, например, чтобы показать баннер;
  • Клиент сам решает, включена фича или нет, в зависимости от предпочтений пользователя.

В фича-флагах в экосистеме .NET мне нравится то, что можно начать с чего-то простого и усложнять по мере необходимости. Как будет показано далее, самые полезные фича-флаги уже реализованы, и их достаточно просто подключить.

Теперь давайте реализуем простой фича-флаг в .NET и расширим его до более мощного решения.

Содержание

  1. Создание необходимых условий.
  2. Реализация базового фича-флага.
  3. Настраиваемый фича-флаг.
  4. Введение стандартов с помощью пакета Feature Management.
  5. Встроенные фильтры.
  6. Написание кастомной реализации фильтра.
  7. Использование параметров с кастомными фильтрами.
  8. FeatureGate для контроллеров MVC.
  9. Azure Feature Management.
  10. Заключение.
Больше полезных материалов вы найдете на нашем телеграм-канале «Библиотека шарписта»

Создание необходимых условий

Создадим новое приложение со следующими командами.

        dotnet new webapi -minimal -o FeatureFlagsInNet
cd .\FeatureFlagsInNet
    

Теперь, откроем приложение в IDE и откроем файл Program.cs. Здесь вы найдёте код, который уже реализует эндпоинт GET для получения набора прогнозов погоды.

Сокращенная версия кода только с самым необходимым (убрана реализация swagger) выглядит следующим образом.

Program.cs
        var builder = WebApplication.CreateBuilder(args);
 
var app = builder.Build();
app.UseHttpsRedirection();
 
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", () =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
    

Чтобы проверить, что всё работает, запустим приложение и зайдём на https://localhost:{portnumber}/weatherforecast (не забудьте поменять номер порта).

        dotnet watch run
    

Если всё хорошо, вы увидите набор прогнозов. Вот что показывается для меня.

weatherforecasts.json
        [
    {
        "date": "2022-06-18T14:38:57.4853538+02:00",
        "temperatureC": -4,
        "summary": "Freezing",
        "temperatureF": 25
    },
    {
        "date": "2022-06-19T14:38:57.4853797+02:00",
        "temperatureC": -7,
        "summary": "Scorching",
        "temperatureF": 20
    },
    {
        "date": "2022-06-20T14:38:57.48538+02:00",
        "temperatureC": -10,
        "summary": "Cool",
        "temperatureF": 15
    },
    {
        "date": "2022-06-21T14:38:57.4853802+02:00",
        "temperatureC": -9,
        "summary": "Mild",
        "temperatureF": 16
    },
    {
        "date": "2022-06-22T14:38:57.4853804+02:00",
        "temperatureC": 20,
        "summary": "Bracing",
        "temperatureF": 67
    }
]
    

Реализация базового фича-флага

Теперь мы можем реализовать фича-флаг в эндпоинте прогнозов погоды.

Для простоты скажем, что эндпоинт включается или выключается в зависимости от флага.

Самый простой фича-флаг — это обычный оператор if, зависящий от булева выражения. Для реализации фича-флага определим переменную forecastEnabled. Значение этой переменной определяет значение, возвращаемое эндпоинтом:

  • true (фича-флаг включен), эндпоинт возвращает данные прогнозов;
  • false (фича-флаг выключен), эндпоинт возвращает код состояния 404.
Program.cs
        var builder = WebApplication.CreateBuilder(args);
 
var app = builder.Build();
app.UseHttpsRedirection();
 
var forecastEnabled = true;
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", () =>
{
    if(!forecastEnabled) {
        return Results.NotFound();
    }
 
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
    

Настраиваемый фича-флаг

Пока в этом нет никакого смысла. Фича-флаг либо включен, либо выключен для всех клиентов. Для включения эндпоинта прогнозов необходимо изменить в коде переменную forecastEnabled и повторно зарелизить приложение.

При этом теряется смысл фича-флагов. Чтобы убедиться, что наш фича-флаг полезен, сперва необходимо найти способ динамически менять его значение.

Для этого состояние фичи надо определять вне кода приложения. В результате мы сможем менять настройку во время релиза, а артефакты сборки можно будет использовать на нескольких стадиях CI/CD-пайплайна.

Пример: фича-флаг может быть включен в стейджинге и выключен в продакшене.

Для настройки состояний фича-флага можно использовать appsettings. Таким образом, мы добавим дополнительную секцию FeatureFlags, в которой создадим свойство WeatherForecast.

appsettings.json
        {
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "FeatureFlags": {
        "WeatherForecast": true
    }
}
    

Чтобы использовать новую конфигурацию, заменим переменную forecastEnabled значением из appsetings.

Program.cs
        var builder = WebApplication.CreateBuilder(args);
 
var app = builder.Build();
app.UseHttpsRedirection();
 
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", () =>
{
    if (!builder.Configuration.GetValue<bool>("FeatureFlags:WeatherForecast"))
    {
        return Results.NotFound();
    }
 
    var forecast =  Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
    

Введение стандартов с помощью пакета Feature Management

Вместо определения своих стандартов мы можем использовать реализацию Майкрософта из пакета NuGet Microsoft.FeatureManagement.AspNetCore.

Используем следующую команду для установки пакета.

        dotnet add package Microsoft.FeatureManagement.AspNetCore
    

После установки пакета можно провести рефакторинг нашей собственной реализации с помощью базовой. Для этого:

  • Импортируем пространство имён Microsoft.FeatureManagement;
  • Зарегистрируем службу AddFeatureManagement;
  • Внедрим IFeatureManager в эндпоинт;
  • Используем метод IFeatureManager.IsEnabledAsync, чтобы проверить, включена ли фича.

ASP.NET v7 представил EndpointFilter, который можно использовать с минимальными API. Это удобно, так как вы можете с лёгкостью повторно использовать логику фильтрации и не захламлять ею ваш эндпоинт (в этом случае — проверяя, включена ли фича). Для создания многоразового фильтра эндпоинта для ваших фича-флагов можно прочитать мою статью Implementing a Feature Flag based Endpoint Filter

Program.cs
        using Microsoft.FeatureManagement;
 
var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddFeatureManagement(builder.Configuration.GetSection("FeatureFlags"));
 
var app = builder.Build();
app.UseHttpsRedirection();
 
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", async (IFeatureManager manager) =>
{
    if (!await manager.IsEnabledAsync("WeatherForecast"))
    {
        return Results.NotFound();
    }
 
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
    

Конфигурация в файле appsettings.json не требует изменений и может повторно использоваться как есть. Этот шаг немногое решает сам по себе, но даёт возможность сделать большее…

Встроенные фильтры

Помимо определённого и последовательного способа настройки фича-флагов, пакет предоставляет несколько реализаций фича-флагов (фильтров) для наиболее распространённых ситуаций.

С пакетом Feature Management мы можем включать или выключать фича-флаг (true или false), как делали раньше. Но у пакета также есть понятие «фильтров». У каждого фильтра есть своя схема конфигурации (в JSON), но способ проверки, включена ли фича, остаётся неизменным для всех фильтров.

В дополнение к встроенным фильтрам можно писать собственные реализации.

Доступны следующие фильтры:

  • PercentageFilter для случайного включения/выключения фичи в зависимости от процента;
  • TimeWindowFilter для включения фичи во время заранее определённого окна с начальным и конечным временем;
  • TargetingFilter, для включения фичи указанному пользователю или группе пользователей (для этого фильтра тоже можно установить процент).

Для большей информации можно взглянуть на документацию.

Насколько я знаю, фильтр TargetingFilter нельзя использовать, если используется структура минимального API. При регистрации фильтра вы получите ошибку:

        System.InvalidOperationException: Unable to resolve service for type 'Microsoft.FeatureManagement.FeatureFilters.ITargetingContextAccessor' while attempting to activate 'Microsoft.FeatureManagement.FeatureFilters.TargetingFilter'.
    

Эти фильтры доступны в пространстве имён Microsoft.FeatureManagement.FeatureFilters и должны быть зарегистрированы по отдельности.

Остальная часть кода остаётся прежней. Другими словами, это не меняет то, как мы проверяем, включен ли фича-флаг.

Program.cs
        using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;
 
var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddFeatureManagement(builder.Configuration.GetSection("FeatureFlags"))
    .AddFeatureFilter<PercentageFilter>()
    .AddFeatureFilter<TimeWindowFilter>();
 
var app = builder.Build();
app.UseHttpsRedirection();
 
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", async (IFeatureManager manager) =>
{
    if (!await manager.IsEnabledAsync("WeatherForecast"))
    {
        return Results.NotFound();
    }
 
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
    

Как было сказано ранее, мы используем фича-флаги как раньше, но настраиваем их иначе.

Ниже показано, что тип фильтра определяется со свойством Name. Со свойством Parameters конфигурация фильтра установлена на фильтры процента и временного окна.

appsettings.json
        {
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "FeatureFlags": {
        "WeatherForecastPercentage": {
            "EnabledFor": [
                {
                    "Name": "Microsoft.Percentage",
                    "Parameters": {
                        "Value": 50
                    }
                }
            ]
        },
        "WeatherForecastTimeWindow": {
            "EnabledFor": [
                {
                    "Name": "Microsoft.TimeWindow",
                    "Parameters": {
                        "Start": "Mon, 06 Jun 2022 00:00:00 GMT",
                        "End": "Mon, 13 Jun 2022 00:00:00 GMT"
                    }
                },
                {
                    "Name": "Microsoft.TimeWindow",
                    "Parameters": {
                        "Start": "Mon, 04 Jul 2022 00:00:00 GMT",
                    }
                },
                {
                    "Name": "Microsoft.TimeWindow",
                    "Parameters": {
                        "End": "Mon, 11 Jul 2022 00:00:00 GMT"
                    }
                },
            ]
        }
    }
}
    

Ниже показан пример конфигурации для фильтра целевой аудитории. Вместо свойства Parameters, фильтр настроен со свойством Audience.

appsettings.json
        {
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "FeatureFlags": {
        "WeatherForecastTargeting": {
            "EnabledFor": [
                {
                    "name": "Microsoft.Targeting",
                    "parameters": {
                        "Audience": {
                            "Users": ["username1", "username2"],
                            "Groups": [
                                {
                                    "Name": "BetaUsers",
                                    "RolloutPercentage": 100
                                },
                                {
                                    "Name": "Internal",
                                    "RolloutPercentage": 75
                                }
                            ],
                            "DefaultRolloutPercentage": 0
                        }
                    }
                }
            ]
        }
    }
}
    

Написание кастомной реализации фильтра

Для реализации собственной логики фильтра создадим класс, реализующий интерфейс IFeatureFilter, и зарегистрируем службу.

Интерфейс IFeatureFilter позволяет реализовать метод EvaluateAsync. Здесь необходимо прописать собственную логику, чтобы понимать, включена фича или нет.

Метод получает аргумент FeatureFilterEvaluationContext, который содержит объект конфигурации. Для получения доступа к параметрам фильтра используем метод FeatureFilterEvaluationContext.Parameters.Get<T>().

Program.cs
        using Microsoft.FeatureManagement;
 
var builder = WebApplication.CreateBuilder(args);
 
builder.Services
    .AddFeatureManagement(builder.Configuration.GetSection("FeatureFlags"))
    .AddFeatureFilter<MyCustomFilter>();
 
var app = builder.Build();
 
app.UseHttpsRedirection();
 
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", async (IFeatureManager manager) =>
{
    if (!await manager.IsEnabledAsync("WeatherForecast"))
    {
        return Results.NotFound();
    }
 
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
 
[FilterAlias(nameof(MyCustomFilter))]
public class MyCustomFilter : IFeatureFilter
{
    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext evaluationContext, CancellationToken cancellationToken = default)
    {
        var settings = evaluationContext.Parameters.Get<MyCustomFilterSettings>()
            ?? throw new ArgumentNullException(nameof(MyCustomFilterSettings));
        return Task.FromResult(settings.LuckyNumber == 47);
    }
}
 
public class MyCustomFilterSettings
{
    public int LuckyNumber { get; set; }
}
    

С помощью атрибута FilterAlias устанавливается имя фильтра. Это то же имя, которое необходимо использовать в конфигурации, чтобы соединить её с фильтром.

appsettings.json
        {
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "AllowedHosts": "*",
    "FeatureFlags": {
        "WeatherForecast": {
            "EnabledFor": [
                {
                    "name": "MyCustomFilter",
                    "parameters": {
                        "LuckyNumber": 47
                    }
                }
            ]
        }
    }
}
    

Использование параметров с кастомными фильтрами

Фильтр также может получать параметры, известные только во время выполнения. Например, это может быть альтернативой фильтру TargetingFilter для минимальных API, потому что он способен получать контекст HTTP или сабсет из него.

Для создания фильтра с параметрами заменим IFeatureFilter интерфейсом IContextualFeatureFilter. После такого изменения метод EvaluateAsync будет получать дополнительные параметры.

В примере ниже заголовок считывается и посылается в фильтр, но вместо заголовка мог быть HttpContext, ClaimsPrinciple или что-то ещё.

Program.cs
        using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement;
 
var builder = WebApplication.CreateBuilder(args);
 
builder.Services
    .AddFeatureManagement(builder.Configuration.GetSection("FeatureFlags"))
    .AddFeatureFilter<MyCustomFilter>();
 
var app = builder.Build();
 
app.UseHttpsRedirection();
 
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", async (IFeatureManager manager, [FromHeader(Name = "X-Lucky-Number")] int? inputNumber) =>
{
    if (!await manager.IsEnabledAsync("WeatherForecast", new MyCustomFilterContext { InputNumber = inputNumber ?? 0 }))
    {
        return Results.NotFound();
    }
 
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
 
[FilterAlias(nameof(MyCustomFilter))]
public class MyCustomFilter : IContextualFeatureFilter<MyCustomFilterContext>
{
    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext evaluationContext, MyCustomFilterContext context, CancellationToken cancellationToken = default)
    {
        var settings = evaluationContext.Parameters.Get<MyCustomFilterSettings>()
            ?? throw new ArgumentNullException(nameof(MyCustomFilterSettings));
        return Task.FromResult(settings.LuckyNumber == context.InputNumber);
    }
}
 
public class MyCustomFilterSettings
{
    public int LuckyNumber { get; set; }
}
 
public class MyCustomFilterContext
{
    public int InputNumber { get; set; }
}
    

FeatureGate для контроллеров MVC

Эта часть доступна, только если ваше приложение построено на контроллерах и без структуры минимального API.

Вместо проверки включена ли фича в коде, вы можете использовать атрибут FeatureGate, чтобы выключить весь контроллер или определённый эндпоинт. Параметр, передающийся атрибуту, — это имя фичи, установленное в конфигурации.

        using Microsoft.FeatureManagement.Mvc;
 
[FeatureGate("WeatherForecast")]
public class WeatherForecastController : ControllerBase
{
}
    
        using Microsoft.FeatureManagement.Mvc;
 
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    [FeatureGate("WeatherForecast")]
    public async IActionResult Get()
    {
    }
}
    

Azure Feature Management

Пока всё идёт хорошо, но можно сделать еще лучше.

У текущей реализации есть один недостаток. При изменении конфигурации необходимо перезапустить приложение, чтобы изменения вступили в силу. Это влияет на пользователей.

Если вы хотите поменять фича-флаг без перезапуска приложения, вы можете использовать функцию Feature Management от Azure. Это позволит динамически менять конфигурацию фича-флагов.

Прочитайте документацию о том, как устанавливать и использовать фича-флаги в Azure (таблица цен).

В портале Azure можно убирать определения фича-флагов, а также настраивать их фильтры. Это похоже на то, что у нас было в файле appsettings.json, но теперь с пользовательским интерфейсом.

К примеру, посмотрите на следующий скриншот, определяющий фича-флаг WeatherForecast в портале Azure. Ещё здесь можно заметить, что к фича-флагу добавляется ярлык, что удобно при создании групп фич.

Конфигурация Feature Management в портале Azure
Конфигурация Feature Management в портале Azure

Вид конфигурации фича-флага выглядит следующим образом:

Изменение фича-флага WeatherForecast
Изменение фича-флага WeatherForecast

Чтобы использовать Azure Feature Management в коде, для начала установим необходимые пакеты NuGet.

        dotnet add package Microsoft.Azure.AppConfiguration.AspNetCore
dotnet add package Microsoft.Extensions.Configuration.AzureAppConfiguration
    

Прежде чем мы сможем использовать Azure Feature Management, понадобится настроить Azure App Configuration. Во время настройки мы подключаемся к указанному ресурсу Azure и включаем фича-флаги. Помимо этого, есть опция определения, какие фича-флаги надо зарегистрировать в зависимости от их ярлыка.

Конфигурацию Azure можно править под ваши нужды и дальше, например установить длительность кэша. Для дополнительной информации о всех вариантах посмотрите документацию.

В примере ниже мы считываем все фича-флаги без определённого ярлыка и все фича-флаги для текущего окружения. Здесь важен порядок, потому что последнее перекрывает первое. Использование такого подхода упрощает включение фич для всех окружений (фича-флаги без ярлыка), исключая продакшн (фича-флаги с ярлыком Production).

Используя Azure Feature Management, не забудьте зарегистрировать функцию управления фича-флагами и убедиться, что используются фильтры, также как и в предыдущих примерах без Azure.

        using Microsoft.FeatureManagement;
using Microsoft.FeatureManagement.FeatureFilters;
using Microsoft.Extensions.Configuration.AzureAppConfiguration;
 
var builder = WebApplication.CreateBuilder(args);
 
builder.Host.ConfigureAppConfiguration((config, appBuilder) =>
{
    appBuilder.AddAzureAppConfiguration(azureConfig =>
    {
        azureConfig
            .Connect("Endpoint=https://name.azconfig.io;Id=id")
            // include all settings without a label
            .Select(KeyFilter.Any, LabelFilter.Null)
            // include all settings with the label that corresponds to the current environment
            // this overrides the previous setting if there are any
            .Select(KeyFilter.Any, config.HostingEnvironment.EnvironmentName);
        azureConfig.UseFeatureFlags();
    });
});.
 
builder.Services.AddAzureAppConfiguration();
 
builder.Services
    .AddFeatureManagement()
    .AddFeatureFilter<PercentageFilter>();
 
var app = builder.Build();
 
app.UseAzureAppConfiguration();
app.UseHttpsRedirection();
 
var summaries = new[]
{
    "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot"
};
 
app.MapGet("/weatherforecast", async (IFeatureManager manager) =>
{
    if (!await manager.IsEnabledAsync("WeatherForecast"))
    {
        return Results.NotFound();
    }
 
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        (
            DateTime.Now.AddDays(index),
            Random.Shared.Next(-20, 55),
            summaries[Random.Shared.Next(summaries.Length)]
        ))
        .ToArray();
 
    return Results.Ok(forecast);
});
 
app.Run();
 
record WeatherForecast(DateTime Date, int TemperatureC, string? Summary)
{
    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
    

В качестве последнего шага можно убрать настройки фич из файла appsettings.json, так как теперь им владеет Azure.

Заключение

В этой статье мы начали с простого подхода к реализации фича-флага. Для этого требовалось перекомпилировать и заново деплоить приложение, чтобы изменить его поведение.

Первым шагом к лучшему решению был рефакторинг реализации посредством перемещения конфигурации фича-флага из кода в файл appsettings.json. В результате фича-флаг стал декларативным, а приложение стало возможно повторно деплоить (с другим поведением) без повторной компиляции, облегчая процесс выключения определённых фич в указанной среде. В качестве побочного эффекта приложение стало удовлетворять критериям twelve-factor app.

Следующим шагом для ввода стандарта было использование .NET Feature Management API. Дополнительным преимуществом использования этого пакета является возможность ориентироваться на конкретных пользователей. Помимо фича-флагов для всего приложения, их можно настраивать в зависимости от различных условий. Эта функция называется «фильтром», и у Feature Management API есть три встроенных фильтра: PercentageFilter, TimeWindowFilter, и TargetingFilter.

В Feature Management API мне больше всего нравится то, что нет потребности в изменении способа проверки, включен ли фича-флаг, потому что всё решается наличием конфигурации.

Наконец, для максимальной гибкости был добавлен Azure Feature Management. Вместо того чтобы хранить конфигурацию в файле appsettings.json, фича-флаги управляются через портал Azure. Теперь не нужно перезапускать приложение, чтобы увидеть изменения, которые мы вносим в конфигурацию. После внесения этих изменений приложение автоматически обновится во время выполнения через динамическую конфигурацию.

Источники

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ