Feature Flag のメリットとプロダクトへの導入(React + TypeScript)

2021/12/03に公開

最近、プロダクトにLaunchDarklyという Feature Flag を実現するサービスを導入しました。導入自体が楽で実現できることがたくさんあるので、Feature Flagや導入方法についてまとめました。

Feature Flag で実現できること

そもそものFeature Flagについてですが、「コードを変更することなくシステムの振る舞いを変えることができる」[1]ことを指しています。
以下のようにコード上に機能A・機能Bを入れた上でデプロイを行い、外から enableFeature のON/OFFを切り替えることでユーザーに提供する機能を切り替えることができます。

if (enableFeature) {
  doSomethingA();
} else {
  doSomethingB();
}

一見とてもシンプルなFeature Flagですが、色んなことが実現できます。

デプロイとリリースのタイミングの分離

デプロイのタイミングとリリースのタイミングを分けることが可能になります。

例えば、水曜のうちにデプロイ作業を行い木曜の昼にFeature Flagを有効にすることで機能をユーザーに公開するといったことです。
デプロイ=リリースになってる場合、「木曜の12時ちょうどに機能Aを公開したい!」と言ったことを実現することはほぼ不可能でしょう。しかし、Feature Flagを導入していれば12時にFlagを有効にするボタンを押すだけで良くなります。

カナリアリリース

デプロイとリリースのタイミングの分離によってFlagを有効にして全世界に公開することもできますが、機能Aにバグが潜んでいたりすることもあるかと思います。カナリアリリースは、少数のユーザーにのみflagを有効にして機能を公開することで反応を測定する手法です。

弊社ではドッグフーディング[3]を行なっているのですが、全ユーザーに機能Aをリリースする前に社内環境のみFlagを有効にすることでコーポレートの社員にいち早く使ってもらい使用感を確かめてもらっています。

A/Bテスト

カナリアリリースと少し近いですが、A/Bテストを行うことも可能です。
50%のユーザー群Aには画面Aを、もう50%のユーザー群Bには画面Bを見せることで、どちらのUIがよりユーザーの行動に影響を与えるかといったことを検証するために用いるユーザーテストの手法です。

LaunchDarklyを導入する

上ではFeature Flagとそのメリットについて紹介しましたが、導入に際して懸念点もあります。規模が大きいサービスほどFlagの作成から使用を経て削除するまでの管理コストが発生します。

LaunchDarklyというサービスを導入しました。Feature Flagを実現するサービスは複数ありますが、広く使われているということと機能の豊富さで決めました。


フィーチャーフラグサービスの機能と各社の提供状況[4]

Flagの作成やON/OFFの切り替えやターゲティングの設定などが簡単にできます。プランによりますが、レビュー機能もあるため誤操作などを防ぐ工夫もされています。

TypeScriptの型定義生成

LaunchDarklyがREST APIを提供しているので、APIのレスポンスから型定義の生成することにしました。launchdarkly-api-typescriptをインストールすることで使えるようになります。

$ yarn add -D launchdarkly-api-typescript
型定義の生成スクリプト
import { writeFileSync } from 'fs';
import { Configuration, FeatureFlagsApi } from 'launchdarkly-api-typescript';

const apiToken = `${LAUNCH_DARKLY_API_TOKEN}`;
const config = new Configuration({ apiKey: apiToken });
const apiInstance = new FeatureFlagsApi(config);

const projectName = 'project_name';

apiInstance
  .getFeatureFlags(projectName)
  .then(res => {
    const { items } = res.data;
    let typesBody = `export type LDFeatureType = {\n`;
    items.forEach(item => {
      typesBody += `  ${item.key}: boolean;\n`;
    });
    typesBody += `  [key: string]: unknown;\n};\n`;

    writeFileSync(`src/types/ldFeatureTypes.ts`, typesBody);
  })
  .catch(() => {
    const typesBody = `export type LDFeatureType = {\n  [key: string]: unknown;\n};\n`;
    writeFileSync(`src/types/ldFeatureTypes.ts`, typesBody);
  });

あとは ts-node などでビルド前などに実行するようにnpm-scriptsを追加することで自動で型定義の生成ができます。

Reactへの導入

ReactプロジェクトでLaunchDarklyを導入する場合は launchdarkly-react-client-sdk をインストールします。

$ yarn add launchdarkly-react-client-sdk

Providerから流して使用したいコンポーネントで useFlags を使うことでFlagの値を取得できます。

asyncWithLDProvider の追加
import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk';

(async () => {
  const LDProvider = await asyncWithLDProvider({
    clientSideID: 'your-client-side-id',
    user: {
      "key": "aa0ceb",
      "name": "Grace Hopper",
      "email": "gracehopper@example.com"
    },
    options: { /* ... */ }
  });
  render(
    <LDProvider>
      <YourApp />
    </LDProvider>,
    document.getElementById('reactDiv'),
  );
})();
import { useFlags } from 'launchdarkly-react-client-sdk';
import { LDFeatureType } from 'types/ldFeatureTypes';

export const useLDFlags = () => useFlags() as LDFeatureType;

as 使ってるじゃんという話にはなってしまいますが、useFlags()の返り値はanyとなってるので推論が効いてちょっとハッピーになれます。

JSやReactだけでなく、Java, Go, Ruby, PHP などのAPIも用意されているのでFeature Flagの導入を検討されてる方におすすめです。

https://github.com/orgs/launchdarkly/repositories

脚注
  1. https://martinfowler.com/articles/feature-toggles.html#:~:text=modify system behavior without changing code ↩︎

  2. https://www.boel.co.jp/tips/vol129/#:~:text=ビルドをすることでファイル,作業の一部と言えます。 ↩︎

  3. https://ja.wikipedia.org/wiki/ドッグフーディング#:~:text=自社製品を開発して利用する組織の習慣で[1]、組織が実際の使用法で日々自分たちで製品を利用しながら製品テストを行うことである。 ↩︎

  4. https://codezine.jp/article/detail/14662 ↩︎

Discussion