銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

OpenFeature .NET SDKを試してみた(4) 1.5で導入されたInMemoryProviderを試してみる

しばらく触っていない間に v1.5.1 までリリースされていました。v1.5 で InMemoryProvider が追加され、ようやくflagdなどのProviderを別途用意せずに検証できるようになりました。

github.com

利用方法はドキュメントにもあるようにこれだけなのですが、これだけでは何もフラグ定義がはいっていないので、追加する必要があります。

await Api.Instance.SetProviderAsync(new InMemoryProvider());

フラグ定義はDictionary<string, Flag>型でコンストラクタかUpdateFlagsメソッドの引数で指定します。文字列型のフラグであれば次のようになります。

var flags = new Dictionary<string, Flag>
{
    {
        "myStringFlag", new Flag<string>(
            variants: new Dictionary<string, string>{
                { "key1", "val1" },
                { "key2", "val2" },
                { "key3", "val3" }
            },
            defaultVariant: "key1"
        )
    }
};

await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));

//フラグを取得するとき
var strValue = await client.GetStringValue("myStringFlag", "foo");

Flagを定義する際、variantsとdefaultVariantが必須で、任意でcontextEvaluatorを指定できます。contextEvaluatorについてはContextを試す際にあわせて使ってみようと思います。

さて、数値型、ブーリアン型は文字列型と同様にstringをdoubleやboolに変えれば利用できます。OpenFeature .NETはこの他にDateTimeと今まで挙げた型を要素にするリストと辞書を格納できるようです。これらはobject型として取り扱うようになっていました。

まず、DateTimeのときです。variantsを定義するときに、Dictionary<string, Value>として、ValueのコンストラクタにDateTimeを指定します。ValueではなくDateTimeを直接指定すると、フラグを取り出すときにSDK内で行う型チェックで値が取得できなくなりました。 フラグを取り出すときは、GetObjectValue(もしくはGetObjectDetails)を呼び出し、object型で返ってきた値をAsDateTimeでDateTimeとして取り出します。ここはInMemoryProviderに限らず同じ処理になります。

var flags = new Dictionary<string, Flag>
{
    {
        "myDateTimeFlag", new Flag<Value>(
            variants: new Dictionary<string, Value>{
                {
                    "foo", new Value(DateTime.Now)
                },
                {
                    "bar", new Value(DateTime.MaxValue)
                }
            },
            defaultVariant: "foo"
        )
    }
};

await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));

//フラグを取得するとき
var dateTimeValue = (await client.GetObjectValue("myDateTimeFlag", null)).AsDateTime;

リストや辞書の場合は次のようになります。

var flags = new Dictionary<string, Flag>
{
    {
        "myListFlag", new Flag<Value>(
            variants: new Dictionary<string, Value>{
                {"foo", new Value([new Value("foo1"), new Value("foo1")])},
                {"bar", new Value([new Value("bar1"), new Value("bar1")])}
            },
            defaultVariant: "foo"
        )
    },
    {
        "myStructureFlag", new Flag<Value>(
            variants: new Dictionary<string, Value>{
                {
                    "foo", new Value(new Structure(new Dictionary<string, Value>
                    {
                        { "foo1", new Value("foo1") },
                        { "foo2", new Value("foo2") }
                    }))
                },
                {
                    "bar", new Value(new Structure(new Dictionary<string, Value>
                    {
                        { "bar1", new Value("bar1") },
                        { "bar2", new Value("bar2") }
                    }))
                }
            },
            defaultVariant: "foo"
        )
    }
};

await Api.Instance.SetProviderAsync(new InMemoryProvider(flags));

//フラグを取得するとき
var listValue = (await client.GetObjectValue("myListFlag", null)).AsList;
var structureValue = (await client.GetObjectValue("myStructureFlag", null)).AsStructure;
foreach (var (k, v) in structureValue)
{
    //vがstringの場合
    Console.WriteLine($"{k}={v.AsString}");
}

まずリストの場合はValueのコンストラクタにIListもしくはIImmutableListを指定します。実装を見るとなぜこの2つなのかがわかります。上のコードだとコンストラクタが用意されているIListC#12で導入されたコレクション式で生成していることになります。

github.com

辞書型の場合は、辞書のデータをDictionary<string, Value>で用意してStructureのコンストラクタに与えて、StructureをValueのコンストラクタに与えます。マトリョーシカのような構造ですが、これで辞書型を格納したフラグを定義できました。取り出すときは AsStructure プロパティでStructureが取れて、IEnumerable<KeyValuePair<string, Value>>を実装しているのでforeach文などで取り出せます。

ちょっと定義に仕方にくせがある気がしますが、こんな感じになります。