OpenFeatureオレオレプロバイダーやってみる

OepnFeatureって何?

OpenFeatureはフィーチャーフラグのベンダーロックインを避けるために策定された標準仕様です。 CNCF incubating projectにも認定されています。

openfeature.dev

アプリケーションからフィーチャーフラグを使うインターフェースはOpenFeatureが提供し、実際にどのような値を返すかを決定するプロバイダーという部分は各ベンダーなどが提供するという仕組みになっています。

つまり、もしフィーチャーフラグを提供するサービスを変えたくなってもアプリケーションの対応はほとんど必要なくなるということです。

詳しくは、OpenFeatureのintroductionを参照してください。

Introduction | OpenFeature

先ほどもあげたように、OpenFeatureにはプロバイダーという概念があります。これは実際にフラグを出し分ける際に参照される具体的な実装と考えればよいです。

どのプロバイダーを使うかに制約はなく自由です。SaaSを使いたいならSaaSベンダーが提供しているProviderを利用すればよいですし、もちろん自分で実装することもできます。

一例として上げると、フィーチャーフラグSaaSの一種である、FlagSmithはOpenFeatureに対応したプロバイダーを提供しています。

OpenFeature | Flagsmith Docs

OpenTelemetryと世界観は似ていますね。

今回は理解のために自分でプロバイダーを作って使ってみようと思います。

Nodejsでプロバイダーを作ってみる

オレオレプロバイダーを作ってみましょう。今回はNodejsを対象としたプロバイダーを作ってみます。

ドキュメントを読むとOpenFeatureが提供するNodeSDKの中にあるProviderインターフェースを満たすクラスを実装すればよいことがわかります。

export class MyFeatureProvider implements Provider {
  metadata: ProviderMetadata = { name: 'MyFeatureFlagProvider' };
  async resolveBooleanEvaluation(
    flagKey: string,
    defaultValue: boolean,
    context: EvaluationContext,
    logger: Logger,
  ): Promise<ResolutionDetails<boolean>> {
     // フラグの評価時などにcontextを渡せる。contextには好きな値を入れられる。
    // ユーザーごとに出し分けるなどしたいときはユーザーIDなどを入れる
    const userId = context['userId'];
    const result = // flagKeyやcontextの値を元にDBや外部APIなどから値を取得する
    logger.debug('result is ', result);
    return {
      value: result ?? defaultValue,
    };
  }
// あとは返り値がstringだったりobjectだったり、オプショナルだったりなので省略

こんな感じです。指定されたインターフェースを満たすようにクラスを作るだけなので難しくはないです。

プロバイダーを使う

まずはどのプロバイダーを使うかを設定する必要があります。とはいっても引数として先ほど作成したプロバイダーを渡すだけ。

OpenFeature.setProvider(new MyFeatureProvider());

これだけ。環境変数を読むなど初期化をする段階で一緒に与えるとよいでしょう。

プロバイダーを登録すれば準備は完了です。

では使ってみましょう。

const featureClient = OpenFeature.getClient();
// フラグの評価時にキーと一緒に使いたい値を渡す
const client = featureClient.setContext({ userId: '1' });

await client.getBooleanValue("someBoolKey", true); // => 何かしらの真偽値。第2引数はデフォルト値
await client.getStringValue("someStringKey", 'hoge'); // => 何かしらの文字列

実際に触ってみるとキーを渡せる第一引数はstring型になっていることやcontextの型の制約が緩いことに気がつきます。またcontextの値を一律で付与したり渡せる値を制限したくなります。

上の理由からkeyに使える値を型で制約したりコンテキストに渡せる値に制約を掛けたくなって実際には以下のようなラッパーを作ることになると思います。

export const boolFeature = async (
  key: 'hoge' | 'fuga',
  defaultValue: boolean,
  context: { userId: string },
): Promise<boolean> => {
  // このようにクライアントを得るときにコンテキストを渡すこともできる
  const client = OpenFeature.getClient(context);
  return await client.getBooleanValue(key, defaultValue);
};

// このようにフラグの値を取得できる
const flagHoge = await boolFeature("hoge", false, { userId: '1' });

まとめ

OpenFeatureに触れるべく、自分でプロバイダーを実装してみました。

実装するまではフィーチャーフラグを提供するSaaS向けかと思っていました。

しかしSaaSを使わずフィーチャーフラグを自前で実装するという場面でも、OpenFeatureプロバイダーを自分で実装することにより実現することができるということが分かりました。