Gaudiy Tech Blog

Gaudiyの技術、開発組織、カルチャーについてお伝えするブログです

LaunchDarklyを入れてフィーチャーフラグの運用を改善した話

f:id:gaudiy:20211208171540p:plain

こんにちは!エンタメ領域のDXを進めるブロックチェーンスタートアップ、Gaudiyでエンジニアをしている椿(@mikr29028944)です。

僕が所属しているExperienceチーム(ユーザー体験に関わる開発を担う)では、リリースのサイクルを極力短くして、ユーザーの方になるべく早く価値をお届けできるように「フィーチャーフラグ」を活用しています。

今年の7月頃から導入し、チームで運用を続けてきましたが、その中でいくつかの課題も見えてきました。そこで今回は、実際に直面した課題と、Gaudiyがそれに対してどのようにアプローチし、解決してきたかについて、お伝えできればと思います。

フィーチャーフラグを導入してみたけれど、うまく運用できていないチームや、フィーチャーフラグの導入を検討する上で、どのような課題が起こり得るのか把握したい方などにご参考になると嬉しいです!

1. フィーチャーフラグを運用する中での課題

はじめに、フィーチャーフラグの運用を理解いただく上で、Gaudiyのアーキテクチャ構成について簡単に説明させていただきます。

Gaudiyが提供しているコミュニティアプリのアーキテクチャは、下図のようなマイクロサービスアーキテクチャを採用しています。

f:id:gaudiy:20211208010634p:plain
全体のアーキテクチャ構成

ここでは詳細な説明は省きますが、この構成のもとで、新規フラグの追加フローとしては、以下のような形で運用していました。

  1. データベースにフィーチャーフラグを追加
  2. GraphQLのスキーマを修正
  3. フロント側も同様にスキーマを修正
  4. Notionなどのドキュメントにフィーチャーフラグの詳細を記入

ただ、このフローで生じてきた問題は、フィーチャーフラグの実装コストがめちゃくちゃかかる…ということです。特に感じた課題としては、2点ありました。

1-1. 全環境におけるフラグの状態把握がしづらい

データベース上でフラグを管理していたため、全環境×テナントごとに用意されているフラグを、一覧で確認することが不可能でした。

また、いつ、誰によって状態の変更がなされたかの把握が難しく、なにかあった際に誰に聞けばいいのかわからない状態でした。加えて、フラグを追加した人がNotionにその詳細を書くという運用をしていましたが、段々とメンテされなくなり、古いドキュメントのまま残り続けてしまう、といった問題もありました。

1-2. フラグの実装上のルールが統一されてない

さらに、使われなくなったフラグが削除されずに残り続けることで、コードベースを複雑化させてしまっていました。

たとえば、トップのコンポーネントですでに制御されているにもかかわらず、フラグを挿入してしまうなどの無駄が生じる問題や、フラグを挿入する際にリファクタリングで関数を分けるのではなく、そのコード上に上書きしてしまって一つの関数が肥大化していく、みたいな問題が起きていました。

これらに加え、他にも課題がたくさん……

カテゴリ 課題
管理 環境ごとのDBにフラグを用意するコストと運用ミスの発生
管理 Bizサイドなどの開発者以外の人がフラグを変更しにくい
UX 単一ごとのフラグ取得のローディング状態を考える工数がかかり、UXも悪化
設計・開発 実装コストが高く、メンテナンスが大変(※)
設計・開発 フラグを使う方法・宣言がバラバラ
設計・開発 もともとフラグを置く想定をせずにコンポーネント設計をしていたため、
既存のコードに置く際に散在してしまうことがある

※補足すると、BFFにフラグのコードを書いていましたが、フロントからの呼び出しも含めてフィーチャーフラグの実装コストがかかるのと、BFFのないサービス環境もあるため、その場合は実装の差異が生まれてしまってメンテナンスが大変という課題がありました。

2. フィーチャーフラグのツールを調査

これらの課題を解決するため、フィーチャーフラグのよりよい活用の仕方を検討している中で、フィーチャフラグ管理ツールの利用を検討することにしました。

いくつかのツールを調査した結果、Gaudiyでは「LaunchDarkly」というツールがよさそうだ、という結論になりました。

launchdarkly.com

各ツールの比較は、下図のCodeZineさんの記事がわかりやすかったです。

f:id:gaudiy:20211208011431p:plain
参照:https://codezine.jp/article/detail/14662

特にGaudiyの場合は、サービスの品質維持の観点から、リアルタイムでフラグの状態を切り替えられるという点が大きなメリットとしてありました。実際、瞬時にON/OFFが反映できるので、非常に運用しやすいです。

他にも、「ミニマムの場合はコストが抑えられる」「障害が起きてもフラグの状態が保存されるため、サービスの継続性が高い」「公式ドキュメントが充実している」などのメリットがあることから、LaunchDarklyを利用してフラグを実装し、リファクタリングを併せて行う運用に変更しました。

docs.launchdarkly.com

また、LaunchDarklyは、開発する上でのUXが良いのも特徴です。

たとえば、Gaudiyのエンジニアの多くが使用しているエディター、VS Codeのプラグインも備わっています。

このプラグインの特徴としては、以下の3点があります。

  1. ソースコード上で各フィーチャーフラグの現在の値を確認できるため、そのためだけに管理画面に入ったり、どのような値が入るかをデバッグしたりする必要がない。
  2. ソースコード上でフィーチャーフラグのkey入力を自動補完してくれるため、keyのタイポによる意図しないバグが生まれにくい。
  3. VS Code上でフィーチャーフラグの値を変更できるため、変更のために管理画面にいく必要がない。

f:id:gaudiy:20211208011828p:plain
👆key部分をホバーすると、現在のフラグの値が確認できる

f:id:gaudiy:20211208011913p:plain
👆各フィーチャーフラグの値を変更できる

上記のような感じで、エディターのみでフィーチャーフラグの操作を完結でき、ストレスのないフラグ管理を可能にするのが、開発者目線での魅力かなと思います。

3. LaunchDarklyを導入して何が変わったのか

LaunchDarklyの導入と、コードのリファクタリングを実施した結果、以下のように前述した課題を解決することができました。

カテゴリ 課題 結果 詳細
管理 環境ごとのDBにフラグを用意するコストと運用ミスの発生 各環境に自動的にフラグが生成されるようになった
管理 Bizサイドなどの開発者以外の人がフラグを変更しにくい GUIツールで誰でも更新がしやすい
UX 単一ごとのフラグ取得のローディング状態を考える工数がかかり、UXも悪化 フラグを一括で取得することができるようになった
設計・開発 実装コストが高く、メンテナンスが大変 フロントからLaunchDarklyのフラグを参照する形で実装コスト減
設計・開発 フラグを使う方法・宣言がバラバラ フラグの使う方法・宣言をLaunchDarklyのuseFlagsに統一した
設計・開発 もともとフラグを置く想定をせずにコンポーネント設計をしていたため、既存のコードに置く際に散在してしまうことがある リファクタリングを実施して解消した(※後述)

たとえば、フィーチャーフラグの設定一覧はこんな感じです。

f:id:gaudiy:20211208012257p:plain
👆右のトグルボタンのON/OFFでフラグを切り替えられる

また、サービス環境も一覧できます。

f:id:gaudiy:20211208115233p:plain
👆フラグ追加時に右の全環境にフラグが追加される

Gaudiyでは1つのソースで複数のサービス(コミュニティ)を管理しているため、フィーチャーフラグを追加した時に、全サービスに一律でフラグが追加されるのはとても便利です。

また、フィーチャーフラグまわりの課題解決だけでなく、特定のユーザー限定でフラグを有効にできる機能を利用することで、管理者ユーザーのみフラグをONにして、本番環境での動作確認をすることが可能になりました。これによって、テスト環境でのテストから、本番環境へのデータ移行をするコストが大幅に削減できました。

※付け加えると、一部のユーザーにこの機能を適用することで、クローズドβ公開のような限定公開も簡単に実現することができます。

4. 実装の際に注意していること

ツールを導入したことでかなり運用しやすくなりましたが、実装の際に注意している点がいくつかあるので、それについてもご紹介します。

4-1. 親コンポーネントでの制御を意識する

特にフロントエンドでは、バックエンドと比べてフラグが多くなる傾向にあります。そこで、できるだけ親コンポーネント側で制御することを優先しています。

こうすることで、フィーチャーフラグを使っていることを明示できますし、検証が終わったときには削除しやすくなります。

社内での工夫としては、フィーチャーフラグライブラリにあるLaunchDarklyのHooksを、Contextを使ったコンポーネントとして、複合コンポーネントを作って利用しやすくしています。

kentcdodds.com

...

// FeatureFlagを適用するためのコンポーネントを作成
export const FeatureToggle = ({ featureFlagName, children }: Props) => {
  ...
  return <FeatureToggleContext.Provider value={value}>{children}</FeatureToggleContext.Provider>;
};

const On = ({ children }: ChildProps) => {
  const { featureFlagEnabled } = useFeatureToggleContext();
  return <>{featureFlagEnabled && children}</>;
};

const Off = ({ children }: ChildProps) => {
  const { featureFlagEnabled } = useFeatureToggleContext();
  return <>{!featureFlagEnabled && children}</>;
};

FeatureToggle.On = On;
FeatureToggle.Off = Off;

フィーチャーフラグコンポーネントの活用例:

const Group = () => {
  ...
  return (
      <CommunityHome>
         // フィーチャーフラグを活用している箇所が深掘りをしなくてもわかる
        <FeatureToggle featureFlagName="groupTimelineReleaseFlag">
          <FeatureToggle.On>
              <TimelineGroupPage />
          </FeatureToggle.On>
          <FeatureToggle.Off>
              <GroupPage />
          </FeatureToggle.Off>
        </FeatureToggle>
      </CommunityHome>
  );
};

4-2. フィーチャーフラグの種類によって実装方針を定義する

また、フィーチャーフラグの利用法によっても実装の仕方は変わってきます。

Gaudiyでは、主にリリースフラグとアクティベーションフラグとして利用することが多いですが、できるだけコード上に存在しないほうが将来的な負債にならないと考えているため、リリースフラグとして早期に消すことを前提とした使い方が多いです。また、アクティベーションフラグに関しては、利用ケースとしても少し長期でみて、動作を変更したい時に利用しています。

リリースフラグを実装する場合は、例えばhooksの関数を制御したい場合は、同じ処理が重複することがあったとしても関数を2つ設ける方針にしています。これは消しやすさを優先した実装です。

そのため、スコープとしても、リリースフラグを消すところまで開発チームのコンテキストとして抱えるようにしています。

2つの関数に分けてフラグで制御するパターン:

const { singleGroupReleaseFlag } = useFlags();

const handleSubmitV2 = useCallback(
    async (value: TopicCreateValue) => {
        await createOrUpdateTopic(value);
    },
    [createOrUpdateTopic]
  );

const handleSubmit = useCallback(
    async (value: TopicCreateValue) => {
        ... // 他の処理
        await createOrUpdateGroupTopic(value)
    },
    [...]
  );

逆にアクティベーションフラグの場合は、すぐに削除できないものに利用されるため、2つの関数に分けて運用するのではなく、同じ関数内にフラグ制御を設けて使う場合が多いです。

これは長期に滞在することになるため、関連する箇所で不具合が発生した場合に、複数箇所を修正する必要が出てしまうことを避けるための運用です。デメリットとしては、より複雑になってしまうことがありますが、たとえば不具合発生時に、片方しか修正できていなかったなどの問題を考慮しての方針としています。

同一関数内でフラグによる制御するパターン:

const { singleGroupActivationFlag } = useFlags();

const handleSubmit = useCallback(
    async (value: TopicCreateValue) => {
       
   // フラグが適用される場合の処理    
        if (singleGroupActivationFlag) {
            await createOrUpdateTopic(value);
   // それ以外
        } else {
            await createOrUpdateGroupTopic(value);
        }
    },
    [...]
  );

4-3. フィーチャーフラグの追加・更新管理

また、フィーチャーフラグを追加し忘れて実装してしまうことも一部発生していたので、フィーチャーフラグが必要かどうかのチェックと、名前、対象範囲、消すタイミングを開発チケットの段階で記入することで、抜け漏れを防ぐようにしました。

これにより、チケット作成の段階でフィーチャーフラグの考慮をするようになり、実装漏れすることがなくなりました。

f:id:xiaochuan:20211208134605p:plain
👆実際のチケット

5. 解決できた課題とこれから

以上のようにフィーチャーフラグをうまく利用することで、開発コストの大きな削減と品質の向上につなげることができました。

現在はフラグが増えてきたこともあり、不要なフラグをきちんと削除することや、命名ルールなどを揃えてオペレーションミスが発生しづらくするなど、引き続き改善しています。

また、LaunchDarklyの料金プランを上げると、スケジュール機能A/Bテスト機能なども利用できるので、このあたりは今後利用を検討していけたらと思っています。

6. おわりに

今回は、フィーチャーフラグの運用で出てきた課題と、その解決アプローチとしてのLaunchDarkly活用についてご紹介させていただきました。

まだまだ試行錯誤中なので、フラグのうまい利用の仕方や、出し分ける機能の粒度など、他社さんのよいTipsがあったらぜひお話をお伺いしてみたいです。

Gaudiyでは他にも、アジャイル開発を改善していくための取り組みを色々行っているので、開発プロセスの改善に興味のある方がいたらぜひお話ししましょう!

(僕のMeetyです。DDD以外でも全然大丈夫なのでよければ!)

meety.net

Gaudiyの技術選定については、こちらの記事をご参考ください!

techblog.gaudiy.com

毎週水曜日、オープン勉強会やオープンオフィスもやってます!

www.notion.so