銀の光と碧い空

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

OpenFeature .NET SDKを試してみた(3) データ型と評価API

OpenFeature .NET は1/23にv1.4.1がリリースされました。

github.com

今回からこのバージョンで検証したコードを書いていますが、(1),(2)で扱った範囲では SetProviderメソッドが非推奨となり、代わりに非同期なSetProviderAsyncメソッドが導入されました。

var flagdProvider1 = new FlagdProvider(new Uri("http://localhost:8013"));
await Api.Instance.SetProviderAsync(flagdProvider1);

ではOpenFeatureがサポートしているデータ型と評価APIを試してみます。

データ型

OpenFeatureがサポートしているデータ型は以下の5つです。

  • ブール
  • 文字列 (UTF-8エンコード)
  • 数値 (整数、浮動小数点など実装言語により、さらに詳細な型や機能が提供されることがある)
  • 構造型データ(実装言語の慣例でJSONやYAMLなどが使われる)
  • 日付(オプションでタイムゾーン付き、ない場合はUTCとして扱う)

openfeature.dev

評価API

OpenFeature .NETのAPIでは

  • ブールは GetBooleanValue
  • 文字列は GetStringValue
  • 数値は整数型(int)が GetIntegerValue 、浮動小数点型(double)が GetDoubleValue
  • 構造データが GetObjectValue

となっており、日付型に対応したAPIがありません。ただ、ほかの言語のSDKを見ても日付型に対応したAPIの実装が確認できなかったので、.NET 特有の問題ではないようです。仕様のドキュメントにもデータ型への対応については詳細がなく今のところ理由がわかりませんでした。

OpenFeature .NET の評価APIは上に挙げたような命名規則ですが、すべて非同期(async)メソッドになっているので返り値の型はTaskです。他の多くの .NET ライブラリと異なり、XxxAsyncという命名になっていませんが、これは 2.0.0 で変更される方針で議論が進んでいるようです。

github.com

また、ほかのAPIはGetXxxAsyncのXxxがデータ型名を表していますが、構造データを返す GetObjectValue だけはobject(Task)ではなくて、OpenFeature.Model.Value というSDKで定義された型となっています。Value型はOpenFeatureのオブジェクトを扱うためのクラスですが、IsXXXプロパティで中に入っているデータが該当の型であるかどうか判別するAPIと、AsXXXプロパティで中に入っているデータを該当の型で取得するAPIが用意されています。そのため、あらかじめデータ構造がわかっていればAsXXXプロパティでデータを取得できますが、そうでない場合はかなり面倒な処理が必要になります。

一例としてこのようなオブジェクトをflagdに与えているとします。

{
  "flags": {
    "myObjectFlag1": {
      "state": "ENABLED",
      "defaultVariant": "foo",
      "variants": {
        "foo": {
          "foo1": "foo1",
          "foo2": 1,
          "foo3": 1.5,
          "foo4": true
        },
        "bar": {
          "bar1": "bar1",
          "bar2": "bar2"
        }
      },
      "targeting": {}
    },
    "myObjectFlag2": {
      "state": "ENABLED",
      "defaultVariant": "foo",
      "variants": {
        "foo": {
          "foo": ["foo1", "foo2"]
        },
        "bar": {
          "bar": ["bar1", "bar2"]
        }
      },
      "targeting": {}
    }
  }
}

それぞれのフラグを取得する場合のコード例はこうなります。

var val3 = await client.GetObjectValue("myObjectFlag1", null);
dump(val3);
var val4 = await client.GetObjectValue("myObjectFlag2", null);
dump(val4);

static void dump(Value value)
{
    foreach (var (k, p) in value.AsStructure)
    {
        if (p.IsString)
        {
            Console.WriteLine($"{k} (string): {p.AsString}");
        }
        else if (p.IsNumber)
        {
            Console.WriteLine($"{k} (number): {p.AsDouble}");
        }
        else if (p.IsBoolean)
        {
            Console.WriteLine($"{k} (bool): {p.AsBoolean}");
        }
        else if (p.IsList)
        {
            Console.Write($"{k} (list): ");
            foreach (var v in p.AsList)
            {
                Console.Write(v.AsString );
            }
        }
    }
}

構造データを取得する場合、Value型の中身がValueのマップ構造データを表すOpenFeature.Model.Structure型になっています。そのため、まずAsStructureプロパティで取り出します。StructureはIEnumerable<KeyValuePair<string, Value>>を実装しているので、そのあとは上のようにデータ取得することになります。

詳細な評価API

まだブログで取り上げていませんが、OpenFeatureには与えたコンテキストに応じて動的な評価を行うなどさまざまな機能があります。そのような機能を使う場合、評価された値がなぜその値になっているか理由を知りたいことが出てきます。例えば、評価された値がデフォルト値である場合、動的な評価の過程で該当するものがないためデフォルトになったのか、該当するものがあってその評価の結果がデフォルト値と同じ値だったのか確認したいことがあります。そのような、メタデータと呼ばれる値を評価値とあわせて取得するためのAPIがOpenFeatureでは仕様として用意されています。

OpenFeature .NETの場合はGetXXXDetails という命名で、値だけを取得する評価APIと同じように用意されています。使い方は以下のようになります。

var details1 = await client.GetStringDetails("myStringFlag", "def");
Console.WriteLine(details1.FlagKey);
Console.WriteLine(details1.Variant);
Console.WriteLine(details1.Value);
Console.WriteLine(details1.Reason);
if (details1.ErrorType != ErrorType.None)
{
    Console.WriteLine(details1.ErrorType);
    Console.WriteLine(details1.ErrorMessage);
}
else
{
    Console.WriteLine("No error");
}

ReasonとErrorTypeの値の一覧は以下の仕様に記載されています。

openfeature.dev