App Configurationの動的機能フラグ(フィルター)を試す

Azure

前回はApp Condifurationの機能フラグについてまとめました。

機能フラグはTrueかFalseのどちらかの値を持ち、アプリから機能フラグを読み取ることができます。機能フラグの値を更新することで、その変更内容をアプリが定期的に取得し、途中からアプリの挙動を変えたり、新しい機能を追加したりすることができます。前回は機能フラグをそのまま使用したため、Azure Portal上でTrueに設定すれば当然アプリで読み取った際もTrueになりました。しかし、「フィルター」を利用することで、Azure Portal上では機能フラグをTrueに設定していても、アプリで機能フラグを取得する際にFalseに切り替えるという振る舞いを実現できます。条件付き機能フラグとも呼ばれますが、今回はそんなフィルター(条件付き機能フラグ)についてみていきたいと思います。

結論

  • フィルターを使うことで、特定の条件を設定し動的にTrue/Falseを変更させることができる
  • Percentage filterを使うと、機能フラグがTrueになる割合を指定できる
  • Targeting filterを使うと、ユーザーやグループに応じて機能フラグの値を変更することができる

環境

いつも通り、サンプルアプリはAzure Functionsのisolatedで実装して、動作確認を行います。

基本情報

言語.NET 6(6.0.413)
関数の言語isolated(分離ワーカープロセス)
App ConfigurationFree
OSWindows 11

使用する機能フラグ

機能フラグ備考
BetaTruePercentage filterを設定
TargetTrueTargeting filterを設定

フィルター

Azure Portalでの設定

前回は詳細を省略しましたが、Azure Portalでは「Feature filters」と呼ばれる項目で設定します。「Use feature filter」にチェックをいれてCreateボタンを押します。

以下のように3種類のフィルターが存在するようです。

Targeting filter

AさんであればTrue、BさんはFalse、Xグループに属していればTrueなどユーザーやグループに合わせて動的に機能フラグを変更することができます。

Time window filter

特定の期間のみTrueにするといったように、開始終了を選択して時間ベースで動的に機能フラグを変更することができます。

カスタムfilter

名前の通り、自分で条件を設定することができます。Azure組み込みのPercentage filterなどもカスタムfilterから設定します。

様々なフィルターを設定できることが分かりました。そこで今回はこの中から、「Percentage filter」と「Targeting filter」について取り上げます。

Percentage filterの設定と動作確認

Percentage filterとは、指定した割合でTrueになるフィルターです。実際にAzure Portalから設定してみましょう。既存のBetaという名前の機能フラグに対してフィルターを追加します。

機能フラグの設定

Feature filtersにチェックをいれ、Createを押下します。すると右側に詳細設定画面が表示されるため、Filter typeで「Custome filter」を選択します。またCustome filter nameには「Microsoft.Percentage」を、Parameter nameには「Value」、Valueには「50」を指定します。最後のValueは好きな割合を指定します。

アプリの設定

14行目のDI設定の個所で「AddFeatureFilter<PercentageFilter>()」を追加します。

    public static void Main()
    {
        var host = new HostBuilder()
            .ConfigureAppConfiguration(builder =>
            {
                builder.AddAzureAppConfiguration(options =>
                {
                    options.Connect(appcs_con_string).UseFeatureFlags();
                });
            })
            .ConfigureServices(s =>
            {
                s.AddAzureAppConfiguration();
                s.AddFeatureManagement().AddFeatureFilter<PercentageFilter>();
            })
            .ConfigureFunctionsWorkerDefaults(app =>
            {
                app.UseAzureAppConfiguration();
            })
            .Build();
        host.Run();
    }

アプリのコードは省略しますが、DIで受け取ったIFeatureManagerSnapshotを用いて機能フラグBetaを取得します。IsEnabledAsyncの引数は機能フラグの名前を指定します。

private readonly IFeatureManagerSnapshot _featureManagerSnapshot;

public Test1(ILoggerFactory loggerFactory, IFeatureManagerSnapshot featureManagerSnapshot)
{
    _logger = loggerFactory.CreateLogger<Test1>();
    // Program.csの14行目でFeatureFilterをDIしたため、ここでIFeatureManagerSnapshotを受け取ることができる
    _featureManagerSnapshot = featureManagerSnapshot;
}

public async Task<HttpResponseData> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
{
    (略)
    featureEnalbed = await _featureManagerSnapshot.IsEnabledAsync("Beta");
  (略)
}

動作確認

10秒ごとに機能フラグBetaを読み取り、その値をコンソールに出力します。結果は以下の通りになりました。おおよそ50%でTrueとFalseがランダムに表示されていることが分かります。

Targeting filterの設定と動作確認

つづいては、Targeting filterになります。ここではTomだったらTrue、AliceだったらFalse、それ以外は50%の確率でTrueとなるようにフィルターを設定して、動作確認を行います。

機能フラグの設定

今回は新規でTargetという機能フラグを追加します。Percentage filterの時と同じく、Feature filtersにチェックをいれCreateボタンを押下します。ここではタイプは「Targeting filter」を指定します。Default Percentageはそのままにします。Override by UsersにチェックをいれてInclude UsersにTomを、Exclude UsersにAliceを指定して保存します。

アプリの設定

Targeting filterを使う場合は今までより複雑なコードになります。App Configurationへ機能フラグを取得する際に、誰からのリクエストなのかを伝える必要があります。

本来であればログイン情報をもとにしますが、新規にログイン機能を追加するのも大変です。そこで、Azure Functionsのアプリにリクエストを送る際に、そのリクエストボディに{ “name” = “Tom” }という形でユーザー名を指定して、アプリではこのボディから名前を取得しApp Configurationに伝えることにします。

大まかな設定内容は公式ドキュメント通りですが、isolatedなFunctionsではそのままではうまく動作しなかったため少々変更しています。

まずは、公式ドキュメントの通りに「TestTargetingContextAccessor.cs」を新規作成します。

using Microsoft.AspNetCore.Http;
using Microsoft.FeatureManagement.FeatureFilters;

namespace Company.Function
{
    public class TestTargetingContextAccessor : ITargetingContextAccessor
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public TestTargetingContextAccessor(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        }

        public ValueTask<TargetingContext> GetContextAsync()
        {
            var httpContext = _httpContextAccessor.HttpContext;
            var targetingContext = new TargetingContext
            {
                UserId = httpContext.Items.First().Value.ToString()
            };
            return new ValueTask<TargetingContext>(targetingContext);
        }
    }
}

このクラスの役割は、App Configurationに誰からのリクエストであるかを伝えることです。

Targeting filterの場合も、最終的にはIsEnabledAsyncメソッドで機能フラグを取得します。このIsEnabledAsyncメソッドが呼ばれたときに、上記のGetContextAsyncメソッドが呼ばれます。

GetContextAsyncメソッドではリクエスト情報(HttpContext)からユーザー名を取得して、それを18行目でtargetingContextのUserIdに設定して、最後に22行目でtargetingContextを返却しています。

ちなみに初めは、_httpContextAccessor.HttpContextがnullになってしまい、うまくリクエスト情報を取得できませんでした。そのためミドルウェアを作成してそのミドルウェアの中でアプリへのリクエストからユーザー名を抜き出して、HttpContextのItemsに追加することにしました。20行目でHttpContextのItemsからユーザー名を取得できているのは、ミドルウェアで自分でItemsにユーザー名を追加しているからです。では次にそのミドルウェアを見ていきます。

using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;

namespace Company.Function
{
    internal sealed class HttpDataMiddleware : IFunctionsWorkerMiddleware
    {
        public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
        {
            var httpContextAccessor = context.InstanceServices.GetRequiredService<IHttpContextAccessor>();
            var requestData = await context.GetHttpRequestDataAsync();

            var hc = new DefaultHttpContext();
            var bindingData = requestData?.FunctionContext.BindingContext.BindingData.First();
            hc.Items.Add(bindingData.Value.Key, bindingData.Value.Value);

            httpContextAccessor.HttpContext = hc;
            await next(context);

        }
    }
}

12、13行目でAzure FunctionnsのコンテキストからそれぞれhttpContextAccessorとrequestDataを取得します。httpContextAccessorはTestTargetingContextAccessor.csのコンストラクタで受け取っているものになります。requestDataはAzure Functionsへのリクエストです。

15行目でHttpContextを作成します。16行目でFunctionsへのリクエストの中からname=Tomを取得します。リクエストボディから取得してもいいのですが、バインディングコンテキストにもKey-Value形式でリクエストボディのデータが格納されているのでそこから取得しています。そして取得したデータをHttpContextのItemsに追加して、最後にhttpContextAccessorのHttpContextプロパティに15行目で作成したHttpContextを代入します。

ではこれら2つのクラスを作成したので、Azure Functionsの起動設定に組み込みます。

    public static void Main()
    {
        var host = new HostBuilder()
            .ConfigureAppConfiguration(builder =>
            {
                builder.AddAzureAppConfiguration(options =>
                {
                    options.Connect(appcs_con_string).UseFeatureFlags();
                });
            })
            .ConfigureServices(s =>
            {
                s.AddAzureAppConfiguration();
                s.AddFeatureManagement().AddFeatureFilter<TargetingFilter>();
                s.AddHttpContextAccessor();
                s.AddSingleton<ITargetingContextAccessor, TestTargetingContextAccessor>();
            })
            .ConfigureFunctionsWorkerDefaults(app =>
            {
                app.UseAzureAppConfiguration();
                app.UseMiddleware<HttpDataMiddleware>();
            })
            .Build();
        host.Run();
    }

まずは14~16行目のDIの個所を確認します。フィルターとしてTargetingFilterを型指定します。次にAddHttpContextAccessor()を追加してHttpContextAccessorもDIします。これにより、TestTargetingContextAccessorのコンストラクタでHttpContextAccessorを受け取ることができます。最後にTestTargetingContextAccessorもSingletonでDIします。

21行目は自作したミドルウェアを追加する設定です。

最後に機能フラグを取得する個所ですが以下の通り引数を機能フラグ名Targetに変えただけであとはPercentage filterと同様です。

featureEnalbed = await _featureManagerSnapshot.IsEnabledAsync("Target");

ここまでで下準備は完了です。やや複雑だったので全体の流れを見てみましょう。今回作成したコード以外は省略していますが、おおよそ下記の流れで最終的にApp Configurationへ機能フラグ取得のリクエストを送信しています。

動作確認

Tom、Alice、Mike、Krea、Tea、Jackでリクエストを送り、そのレスポンスから機能フラグTargetの値を確認します。TomはTrue、AliceはFalseでそれ以外は50%の確率でどちらかになります。

まとめ

今回は、App Configurationの機能フラグの中でもFeature filterに着目してみてきました。Percentage filterやTargeting filterなど複数のフィルターがあり、要件に合わせて適切なものを選択します。

フィルターを利用することで、一部のユーザーから順番に新機能を開放するといったことを実現できます。

App Configurationの機能フラグを用いることで、アプリを変更・デプロイすることなく外部からその挙動を変更することができて非常に強力なサービスですが、フィルターを利用することでさらに柔軟性を持たせることができます。とても便利な機能だと思いますので、いいなと思ったらぜひ試してみてください。

コメント