Findyの爆速開発を支えるFeature Flagの使い方

こんにちは。

ファインディでソフトウェアエンジニアをしている栁沢です。

ファインディの各プロダクトでは、1日に複数回デプロイしています。

例えば、私が所属するFindy転職のプロダクトでは、1日に6回ほど本番環境にデプロイしています。

高いデプロイ頻度でもデプロイ起因による障害や不具合がほぼ発生しておらず、開発スピードと品質の両立を実現できています。

今回はファインディ社内でのFeature Flagの使い方について詳しく解説します!

Feature Flagを使うことのメリット

Feature Flag(フィーチャーフラグ)は、コードを書き換えることなく特定の機能を有効化や無効化できる開発テクニックです。

Feature Flagで機能を無効化しておくことで、ユーザーに影響がでないように本番環境にコードをガンガン反映させることができるようになります。

これにより次のようなメリットがあります。

  • 開発途中でもどんどんメインブランチにマージできる。そのため、複数人の開発でもコンフリクトがほぼ起こらない。
  • 本番環境で特定のユーザーやセグメントのみに機能を限定公開できるので、リスクを抑えながら機能を公開できる。
  • 万が一本番環境で問題があった場合、機能を無効化させることですぐに切り戻しができる。

Feature Flagの実現方法

Feature Flagの実現方法は、ライブラリやSaaSを使うなど様々な方法がありますが、現在のFindyでは「環境変数」と「ライブラリ」の2パターンが採用されています。

Findy転職プロダクトでは、導入や運用がシンプルな「環境変数」を使ってFeature Flagを実現しています。

  • 環境設定ファイルで環境変数を追加することで機能を有効化させる
  • 環境変数の値で条件分岐を実装して機能をだし分ける

実際のコード例を見てみるとシンプルなことがわかると思います。

envファイルに環境変数を追加することで機能を有効化させます。逆に、環境変数を削除することで機能を無効化できます。

# .env

FEATURE_NEW_LABEL=true

実装コードは、環境変数の値によって条件分岐を実装することで振る舞いを変えています。

export const SampleComponent = () => {
  // 特定の環境変数の値が'true'なら"NEW"というラベルを表示する
  const isEnabledNewLabel = process.env.FEATURE_NEW_LABEL === 'true';

  return (
    <div>
      {isEnabledNewLabel && <span>NEW</span>}
      <span>Sample</span>
    </div>
  );
};

補足:この実現方法の伸びしろとしては、「環境設定ファイルの修正が手間」、「フロントエンドとAPIでFeature Flagが分散している」、「一部のユーザーに部分的に公開がしづらい」という課題があります。そのため別チームでは、ライブラリを使ってAPIから機能のON/OFF情報を取得する仕組みでFeature Flagを実現しているようです。

Feature Flagを使った開発の流れ

ここからは、より具体的にFeature Flagを使った開発の流れを説明していきます。

  1. Feature Flagを追加する
  2. Feature Flagを使って新機能を実装・テストコードを書く
  3. 検証用の環境で動作確認を実施する
  4. 動作確認が完了したら、本番環境で機能を有効化させる
  5. 一定期間の安定稼働を確認できたら、Feature Flagと条件分岐を削除する

1. Feature Flagを追加する

環境変数を追加し、ローカル環境や検証用環境で機能を有効化させます。

# .env.local や .env.qa

FEATURE_NEW_LABEL=true

Tipsとして、早めに検証用環境で機能を有効にしておくことでバグを早く発見しやすくなり、バグ対応の工数を減らせます。(シフトレフトを進められる)

2. Feature Flagを使って新機能を実装・テストコードを書く

環境変数の値によって条件分岐をすることで機能のON/OFFをできるようにします。

export const SampleComponent = () => {
  // 特定の環境変数の値が'true'なら"NEW"というラベルを表示する
  const isEnabledNewLabel = process.env.FEATURE_NEW_LABEL === 'true';

  return (
    <div>
      {isEnabledNewLabel && <span>NEW</span>}
      <span>Sample</span>
    </div>
  );
};

また、機能をON/OFFしたときに、それぞれのケースで振る舞いが壊れないように自動テストを書いておきます。

describe('SampleComponent', () => {
  it('should render "NEW" label when FEATURE_NEW_LABEL is true', async () => {
    process.env.FEATURE_NEW_LABEL = 'true';

    render(<SampleComponent />);

    expect(screen.getByText('NEW')).toBeInTheDocument();
  });

  it('should not render "NEW" label when FEATURE_NEW_LABEL is false', async () => {
    process.env.FEATURE_NEW_LABEL = 'false';

    render(<SampleComponent />);

    expect(screen.queryByText('NEW')).not.toBeInTheDocument();
  });
});

自動テストを書いておくことで、開発途中でもどんどんメインブランチにマージして、自信をもって本番環境にデプロイできます。

3. 検証用の環境で動作確認を実施する

機能開発が完了したら、検証用の環境で動作確認を実施します。

本番環境では機能が無効化されているため、ユーザーに影響ない形で検証を進めることができます。

4. 動作確認が完了したら、本番環境で機能を有効化させる

検証環境で動作確認が完了したら、本番環境で機能を有効化させます。

# .env.production

FEATURE_NEW_LABEL=true

本番環境で深刻な不具合が発生し、機能を緊急で無効にする必要がある場合は、環境変数を削除することで簡単に切り戻しができます。

# .env.production

# 削除
# FEATURE_NEW_LABEL=true

切り戻しを素早くできるのもFeature Flagの強みです。

5. 一定期間の安定稼働を確認できたら、Feature Flagの処理を削除する

最後に、機能が実際に使われて安定稼働していること確認できたら、環境変数と分岐の処理を削除していきます。

export const SampleComponent = () => {
  return (
    <div>
      <span>NEW</span>
      <span>Sample</span>
    </div>
  );
};

Feature Flagの削除時に間違って必要な処理を削除してしまいデグレをおこしてしまうミスはやりがちです。しかし、自動テストがあることで自信をもって削除作業を進められることができます。

まとめ

今回は、Findyの爆速開発を支えるFeature Flagの使い方を紹介しました。

Feature Flagを使うことで、ユーザーに影響ない形で、開発途中でもどんどんメインブランチにマージできます。これにより、本番環境へのデプロイも1日に複数回実施することが可能になります!

個人的に爆速開発を支える1つの要素として、Feature Flagの活用は必須だと感じています!

もしまだ導入していない場合は、この記事をきっかけにぜひトライしてみてください :)

他にもファインディではいろいろなテクニックを使っているので、興味がある方は次の記事も合わせて読んでみてください。

現在、ファインディでは一緒に働くエンジニアを募集中です。

興味がある方はこちらから ↓

herp.careers