newmo 技術ブログ

技術で地域をカラフルに

feature flag 入門と newmo の feature flag 基盤について

こんにちは。Platform Team の tobi (@iam__tobi) です。

本記事では feature flag の基礎的事項の説明と、Platform Team で開発してきた newmo 独自の feature flag 基盤の設計思想と全貌について二段構成でご紹介します。

これから feature flag を導入しようと考えている方にとって参考になれば幸いです。

背景・概要

スタートアップである newmo では、変化に俊敏に追従し、開発サイクルを高速に回すことが事業成長の鍵となります。そのためには「いつでもリリースできる状態」を保ち、機能追加を素早く本番環境に届ける開発フローが欠かせません。 トランクベース開発 *1 は、単一のメインブランチへ小さな変更を高頻度にマージすることで (CI/CD が適切に構築されている場合)、高頻度なデプロイを実現できます。実際 newmo ではトランクベース開発を採用しており、最近では 1 日あたり平均 100 回ほど main へブランチがマージされ、そのたびに開発環境へコードがデプロイされています。

しかしこの状態では、デプロイと機能リリースが同時に行われるため、実験的な機能や不具合が即座に全ユーザーへ露出し、修正やロールバックのたびに再デプロイが必要となるため大きな負担となります。 また、高速な開発サイクルのなかで、開発環境と本番環境の機能差異を単なる環境変数やコードベースの差分だけで実現するのは現実的ではありません。ユーザー属性ごとに機能を出し分けることも困難です。

ここで feature flag が重要な役割を果たします。newmo の Platform Team では、トランクベース開発のスピードと安全性を両立させるため、高信頼・低コスト・開発者フレンドリーな feature flag 基盤をゼロから設計・実装しました。

feature flag 入門

feature flag について、もう一度おさらいしておきましょう。

feature flag を解説している入門記事はたくさんありますが、今回は Pete Hogdson 氏の記事を特に参考にさせていただきました。

本記事ではこの記事を前提に話を進めていきます。

feature flag の概要

記事では feature flag を次のように説明しています。

Feature Toggles (often also referred to as Feature Flags) are a powerful technique, allowing teams to modify system behavior without changing code.

(中略)

“Feature Toggling” is a set of patterns which can help a team to deliver new functionality to users rapidly but safely.

feature flag (feature toggle) は、コードを変更することなくシステムの振る舞いを変えられる仕組みです。これにより開発者は、新機能を迅速かつ安全に提供できます。

feature flag を用いたコードのイメージを示します。

func (s *Service) SomeFunc(ctx context.Context) error {
  if s.featureflag.Evaluate(ctx, "do-experiments-flag") {
    // do something experimental
  } else {
    // do something stable
  }
}

do-experiments-flag は feature flag の名前であり、実験的機能のオン・オフを制御します。これにより、実験的機能のロールアウトを環境ごとに切り替えたり、ユーザーごとに評価結果を変化させたりできます。たとえば、あるセグメントにはフラグ値として true を返して実験機能をリリースし、別のセグメントには false を返してリリースしない、といった制御が可能です。

このようにユーザーごとに値の評価を制御する手法は Dynamic Evaluation *2 と呼ばれ、本記事でも以降頻出します。

feature flag の構成要素

「feature flag を導入する」と一口に言っても、実際には複数の細かな要素が精緻に組み合わさって機能しています。ここでは、feature flag 関連のブログ記事でよく取り上げられる構成要素を紹介します *3。

まず、以下のようなシンプルなコードを想定します。

if featureflag.IsActive("holiday-greeting") {
  fmt.Println("Happy holidays!")
}

(引用: Feature Toggles (aka Feature Flags))

Toggle Point

Toggle Point とは、フラグを判定するコード上の“分岐点”です。上記の例ではIsActive("holiday-greeting") が該当し、ここでフラグの値に応じて実行パスが切り替わります。

Toggle Router

Toggle Router は、多数の Toggle Point に対して一貫した評価結果を提供する単一の情報源 (single source of truth) です。例では featureflag.IsActive がフラグ名を受け取り、Toggle Configuration と Toggle Context を用いて内部ロジックに基づいて真偽値を計算し、結果を返しています。したがって、この関数が Toggle Router の役割を果たします。

Toggle Context

Toggle Context は、Toggle Router がフラグを評価する際に考慮する文脈情報です。例では「現在の日付」が文脈に当たる可能性がありますが、ほかにもユーザー ID・ユーザー属性・参照元 URL など多岐にわたり、ビジネスロジックと深く結びつきます。OpenFeature では evaluation context がこの役割を担います。

Toggle Configuration

Toggle Configuration は、Toggle Router がフラグを評価するために参照する 外部設定 です。たとえば flagd では、以下のような JSON 定義を利用します。flagd の詳細は後述しますが、ここでは「フラグ評価に必要なすべての情報が格納された設定ファイル」と認識していただければ良いです。

{
  "$schema": "https://flagd.dev/schema/v0/flags.json",
  "flags": {
    "new-welcome-banner": {
      "state": "ENABLED",
      "variants": {
        "on": true,
        "off": false
      },
      "defaultVariant": "off",
      "targeting": {
        "if": [
          { "ends_with": [{ "var": "email" }, "@example.com"] },
          "on",
          "off"
        ]
      }
    }
  }
}

feature flag の種類

記事では feature flag を dynamism と longevity の 2  軸で 4  種類に分類しています。

(引用: Feature Toggles (aka Feature Flags))

  • dynamism
    フラグ評価がどれだけ動的か (=実行時にどの程度変化するか) を示します。軸の左側に行くほど値の変化は デプロイ単位、右側に行くほどより細かい粒度の リクエスト単位 で変化すると例示されています。
  • longevity
    フラグがコードベースにどれだけ長く存続するかを示します。

feature flag はコードベースの複雑性を高めるため、カテゴリごとにオーナーシップや運用ポリシーを明確にすることが重要です。フラグを定義する際は dynamism と longevity の特性を考慮し、適切なカテゴリへ分類しましょう。

Release Toggles

ユースケース 開発中の不完全・未テストの機能を、本番にデプロイしつつユーザーには非公開にする。
トランクベース開発を支援し、「機能のリリースとデプロイを分離する」という継続的デリバリーの原則を実現。
dynamism 非常に静的。特定のデプロイでは常に同じ評価結果を返すよう定義される。
longevity ごく短命 (通常 1〜2 週間程度)。

Experiment Toggles

ユースケース カナリアリリース、A/B テスト、多変量テストの実施。
エンドユーザーを cohort (セグメント) に分け、セグメントごとに評価結果を変える。
dynamism 非常に動的。リクエストごとに評価結果が変化。
longevity 安定性の検証や統計的に有意な結果が得られるまで存続。

Ops Toggles

ユースケース システム運用中に動作を制御する。
高負荷時に重要度の低い機能を無効化する Kill Switch など。
dynamism 静的寄り。デプロイ単位ほど静的ではないが、リクエスト単位ほど動的でもない。
longevity 新機能の安定性やパフォーマンスへの影響が把握できるまで存続。場合によっては無期限に残ることもある。

Permission Toggles

ユースケース ユーザー属性に応じて機能/体験を差別化。
例:有料ユーザーのみ「プレミアム機能」を有効化、内部ユーザー向けに「アルファ機能」を公開。 (Champagne Brunch)
dynamism 非常に動的。リクエストごとに評価結果が変化。
longevity プレミアム機能などを管理する場合、数年単位で維持されることもある。

補足
“Champagne Brunch” は内部またはベータユーザー向けに新機能を先行公開する手法の俗称です。大規模サービスで広く採用されています。

OpenFeature

feature flag サービスは、OSS から SaaS (いわゆる  FFaaS) まで多種多様に存在しています。Unleash、Flagsmith、LaunchDarkly、Split.io など、選択肢に事欠きません。しかし、各プロダクトが提供する 独自 SDK をアプリケーションへ組み込むたびに、次のような課題が生じます。

  • ベンダーロックイン
    乗り換え時にコードを全面改修するコストが発生する
  • オブザーバビリティのばらつき
    SDK ごとにテレメトリツールとの統合方法が異なる

こうした断片化を解消するべく誕生したのが OpenFeature です。

(引用: Welcome to OpenFeature)

“OpenFeature is an open specification that provides a vendor‑agnostic, community‑driven API for feature flagging that works with your favorite feature flag management tool or in‑house solution.”

OpenFeature は、ベンダーニュートラルな共通 API/SDK を 10  以上の言語で提供しています。アプリケーション側は client.BooleanValue("some-flag") のように統一的な呼び出しを書くだけでよく、実際のフラグ評価ロジックは Provider プラグイン (flagd・LaunchDarkly・Flipt など) を差し替えるだけで変更できます。主要プロダクトは多言語で Provider を提供されています。  *4。

これにより、次のような利点が得られます。

  • バックエンドを切り替えてもアプリ改修は最小限
  • Hooks 機構で OpenTelemetry など横断的処理を一括注入
  • 自前実装からフルマネージド SaaS まで 同じ SDK で運用可能

feature flag を単なるツールではなく インターフェース として捉えることで、将来の移行・統合・多言語開発にも強い基盤を築ける点が最大の魅力です。

2025  年  7  月  20  日現在、OpenFeature は CNCF の Incubating Project です  *5。

flagd

newmo では feature flag の評価エンジンとして flagd を採用しています。

flagd is a feature flag evaluation engine. Think of it as a ready-made, open source, OpenFeature-compliant feature flag backend system.

flagd は OpenFeature 準拠のインターフェースを実装しており、公式に Provider も提供されているため、最初に利用する OSS として非常に有力な選択肢です。

flagd の評価モードには RPC Evaluation と In-Process Evaluation の 2 種類があります。
RPC Evaluation は、外部プロセスが gRPC を介して flagd プロセスにフラグ評価のリクエストを行う方式です。一方、In-Process Evaluation では flagd の評価エンジンを同一プロセス内の goroutine として動かすため、RPC Evaluation に比べて低レイテンシを実現できます。newmo では Modular Monolith を採用していることから、In-Process Evaluation Mode を選択するのが合理的だと判断しました。

また、「SaaS を使わず自社環境で feature flag を運用したい」 というニーズに対しても、flagd は優れた OSS の候補となります。理由は以下のとおりです。

  • flagd は独自スキーマの JSON ã‚’ Toggle Configuration として採用しており、専用 UI を持たないため 完全な SSoT (Single Source of Truth) を構築しやすい。
  • 評価エンジンをゼロから作るのは大きな労力を要しますが、flagd を使えば既存の仕組みを流用しつつ、エンジン部分の実装知識は不要で運用を開始できため、「feature flag を自社用にカスタマイズしたいが、評価エンジンをフルスクラッチで開発するのは避けたい」 というケースで特に適している。

flagd の Toggle Configuration については 公式ドキュメント に詳細がありますが、以下に例を示します。

{
  "$schema": "https://flagd.dev/schema/v0/flags.json",
  "flags": {
    "new-welcome-banner": {
      "state": "ENABLED",
      "variants": {
        "on": true,
        "off": false
      },
      "defaultVariant": "off",
      "targeting": {
        "if": [
          { "ends_with": [{ "var": "email" }, "@example.com"] },
          "on",
          "off"
        ]
      }
    }
  }
}

flagd の Toggle Configuration は大きく以下の要素で構成されます。

  • state
    • フラグが有効かどうかを示します (true / false の値そのものではなく、利用可能か否かの状態)。
  • variants
    • 取りうる値 (バリアント) とキーのマッピング。
  • defaultVariant
    • エラー発生時にフォールバックするデフォルト値。
    • 通常は false ã‚„ "stable" など、安全な値を設定しておくのが望ましい (誤って機能がリリースされるのを防ぐため)。
  • targeting
    • evaluation context の情報と組み合わせて、誰にどの値を返すかを決定するルール。JsonLogic で記述する。
    • 上記例では、evaluation context の email が @example.com で終わる場合は true、そうでなければ false を評価するルール。

このように flagd のシンプルな構成はカスタマイズ性と運用の容易さを両立させており、newmo の既存の Platform とも非常に相性が良いです。

フラグ評価の流れ

ここまで抽象的な説明が続いたので、実際に feature flag がどのように 設定 → 呼び出し → 評価 されるのかを、OpenFeature + flagd の構成を例におさらいします。

フラグ評価の流れ

まず Toggle Point で、OpenFeature Client のインターフェースでフラグの値の評価を試みます。またこの時、beta_user=true という evaluation context (Toggle Context) も同時に与えています。

次に Toggle Router が呼び出されます。Toggle Router の中では flagd が常に動いており、flagd は

  • 外部ソースを定期的に確認して常に最新の Toggle Configuration をメモリにロードしている
  • リクエストを受けたらメモリの Toggle Configuration とリクエスト内容 (フラグ名、Toggle Context) から値を評価し、結果を返す

というサイクルを続けています。

Toggle Configuration は、flagd のものを例に出しています。targeting の意味は、「渡された evaluation context に beta_user のキーがあり、値が true であるならばフラグ結果として true、 そうでないならば false」です。

Toggle Configuration の保存場所は、S3、GCS、コンテナ内のローカルファイルシステム、gRPC 経由などさまざま用意されています。FFaaS だと 専用 UI から Toggle Configuration を設定して実際にはクラウドに保存されているでしょう。

flagd を前提におさらいしましたが、他の OSS や FFaas でも

  • Toggle Router のなかで動いている評価エンジンや OpenFeature Provider が OSS/FFaaS に依存
  • Toggle Configuration のスキーマや設定の仕方、保存場所

こそ異なりますがおおまかなフラグ評価の流れは同じです。

さてここまでを踏まえると、今回のリクエストでは Toggle Context として beta_user=true を渡しているので true が評価されて返ってくることが理解できます。

feature flag 導入の技術的負債

feature flag は決して銀の弾丸ではありません。むしろ 技術的負債を生み出しやすい ことが、欠点として頻繁に指摘されます。代表的な問題は次のとおりです。

  • Zombie Flags
    役目を終えたフラグがコードに残り、条件分岐が肥大化する問題です。誰が・いつ実装したか履歴が曖昧だと、そもそも削除してよいフラグか判断できません。

  • Flag Proliferation (フラグの増殖)
    フラグが増えすぎると、テストケースが爆発的に増加し、テストカバレッジが低下します。複数フラグの組み合わせでバグが潜在化しやすくなる点も厄介です。

  • フラグ名不一致
    フラグ名を magic string として直接コードに書くと、OSS・SaaS 側で設定したフラグ名との不一致が起こりやすくなります。フラグが存在しない/タイプミスといった単純ミスが、意図しない分岐を招くリスクもあります。

これらの負債は feature flag 自体の価値を大きく損ないかねません。feature flag による負債を抑えつつメリットを最大化するためには、ガバナンスとオートメーション が欠かせません。

newmo の feature flag 基盤

newmo feature flag の利点を最大限に活かしつつ欠点を仕組みで抑え込み、何よりも 開発者の生産性向上 を優先した基盤を構築しました。本章では、その設計思想と前提条件を概観します。

前提

newmo では、サーバーサイド実装には Modular Monolith アーキテクチャを採用しています。機能ごとに分割された各サービスを component と呼び、相互通信には gRPC を用います。この記事で component という語が登場した場合は、「Modular Monolith を構成する 1  モジュール」の意味だと捉えてください。*6

設計思想

ここでは、次の 4  つの観点から newmo の feature flag 基盤の設計思想を説明します。

1. 認知負荷の軽減

Platform Engineering の文脈で頻出する「開発者の認知負荷をいかに下げるか」という課題に対し、newmo ではとくに 宣言的定義・自動生成・fire and forget の 3  点に注目して設計しました。

A. 宣言的定義
feature flag の独自スキーマを Protocol Buffers で定義し、そのスキーマに従って YAML でフラグを記述します。開発者は簡潔で明瞭なインターフェースで Toggle Configuration を宣言でき、GitHub の  main  ブランチが「全環境で有効なフラグ状態」の SSoT (Single Source of Truth) となります。過去の設定も Configuration as Data として完全にバージョン管理されています。

B. 自動生成
A によって定義された Toggle Configuration を入力に、各 OSS/FFaaS 向け Toggle Configuration と Toggle Router を自動生成します。Toggle Context を扱う内部ロジックは画一的に実装して Router から切り離し、各 component 用には薄いラッパーのみを自動生成するため、呼び出すだけで透過的に Dynamic Evaluation が機能します。さらにテスト専用のオンメモリストアとヘルパーも自動生成し、テストケースごとに任意の値を override することも可能です。これによりフラグの取り得る値は enum として型安全に扱え、フラグ名の不一致といったヒューマンエラーを原理的に排除できます。

A でスキーマを丁寧に設計していれば、Toggle Configuration と Toggle Router は原理的に自動生成できるはずです。なぜなら各 OSS, SaaS 向けの Toggle Configuration は独自スキーマの Toggle Configuration 表現の部分集合になっており、Toggle Router は複雑な内部ロジックを切り離すと単に Toggle Context とフラグ名で問い合わせて、フラグ評価結果を返す責務しか負っていないからです。

C. fire and forget
Zombie Flag 対策としては一般に「expiration date の付与」「削除チケットの発行」「Slack 通知」などが提案されますが、開発者の認知負荷が最も低いのは「フラグを定義したら、不要になったとき自動で消える」状態だと考えました。newmo では Toggle Point こそがフラグの存在証明であると定義し、定期ジョブでフラグごとの Toggle Point 数を計測して参照されなくなったフラグを自動削除しています。

こうして 定義 → 生成 → 削除 までのライフサイクルをすべて独自基盤で一元管理しています。各項目の詳細は後述します。

2. 低コスト

「コスト」は component の管理コスト、金銭的コスト、新たなフラグを定義する心理的コストの 3  側面で評価しています。

まず管理コストについて、newmo ではワークロードに Cloud Run を採用していますが、サービスを 1  つ増やすごとに管理対象が増えるのは得策ではありません。そこで flagd の In‑Process Evaluation mode *7 を利用し、Modular Monolith 内で評価エンジンを動かしています。これにより外部サービスを増やさずに済み、運用負荷を極小化できました。

金銭的コストも、flagd をベースにした自前実装であるためランニングコストを基本的に少額に抑えられます。前述のとおり追加の Cloud Run は不要なのでインフラ費用も増えません。

心理的コストについては、認知負荷削減の施策により YAML ファイルを作成・更新し PR を出すだけでフラグを定義できるため、UI 操作や手動設定がなく、開発者は迷わずフラグを追加できます。

3. 既存の仕組みとの統合

newmo のアプリケーションにおける認証・認可は Security Token Service Pattern を用いています。API Gateway レイヤでユーザーの Access Token を Security Token Service に渡し、内部トークンへ変換したうえで gRPC を介して各 component に伝播させています。この内部トークンには sub (ユーザー ID) や各種属性が含まれるため、feature flag の Toggle Context としてそのまま利用可能です。既存認証基盤に自然に組み込めるため開発者は違和感なく Toggle Configuration を記述できます。

(引用: Microservices Security Cheat Sheet)

4. 高信頼性

feature flag 基盤は高い信頼性が必須です。可用性がボトルネックになるケースは現在ありませんが、Toggle Configuration の自動生成や Toggle Context のロジックにバグがあれば即インシデントにつながります。そのため自動生成コードは Golden Files Test *8 で差分を検出し、評価ロジックは fake-gcs-server を立てて実際にエンジンを動かし、多様な条件でフラグ評価を行うことで実環境に近い形でテストカバレッジを確保しています。さらに評価エンジンの Metrics・Trace・Log は OpenTelemetry Collector を経由して Datadog へ送信し、運用面の可観測性も担保しています。*9

その他に、ドキュメント整備や独自 OpenFeature Provider の開発、多言語・多プロセスへの展開など、将来拡張も視野に入れて基盤を進化させており、今後も高い信頼性を保ちながら機能拡充を図る予定です。

newmo における feature flag の分類

newmo では Pete Hodgson 氏の 4 分類を参考にしつつ、プロダクト特性に合わせて定義を再考しました。

nemwo における feature flag の分類

大きな相違点は三つあります。第一に Release Toggles の位置づけが右 (=より動的) へ寄っていること、第二に Static Toggles という新しいカテゴリを設けたこと、第三に Ops Toggles と Permission Toggles を現時点では定義していないことです。

Static Toggles はサーバー初期化時の DI や外部サービスのバージョン切り替えなど、環境変数に近い用途で使うフラグです。ただし再デプロイを伴わずに振る舞いを変えられる点が大きな違いで、新規サービスの導入検証や環境ごとのサービス切り替えなどに適しています。

Release Toggles は機能をリリースするかどうかを true / false で判定するフラグで、Hodgson 氏の定義と異なり ユーザー情報を Toggle Context として持つ ことを前提にしています。そのため環境別のロールアウト制御に加え、内部ユーザー限定の QA リリースのようなユースケースにも活用できます。

Experiment Toggles はユーザーを複数のセグメントに分割し、各セグメントに対して任意のパーセンテージで任意の文字列を返すフラグです。A/B/n テストやカナリアリリースなど、より細かな実験・検証で効果を発揮します。

再定義の背景として、newmo ではドライバー向けアプリなどモビリティ関連プロダクトを提供していますが、リリース由来のバグが重大インシデントにつながります。そのため「特定ユーザーだけを安全に対象化する」という要件が多く、Release Toggle の dynamisim を上げてユーザー属性を考慮できるようにしています。

また Experiment Toggle が許容する dynamism は Release Toggle の superset であるため、2 カテゴリのレンジが一部重なるよう図示しています。開発者は A/B/n テストやカナリアリリースをする場合や存続時間が長いと予想される場合には Experiment Toggle を、そうでない場合は Release Toggle を利用します。図は Pete Hodgson 氏のオリジナル図の下位領域を拡張したイメージで、現状ユースケースのない Ops Toggle と Permission Toggle は定義していませんが、将来的に必要になれば上方向へ領域を拡張する想定です。

アーキテクチャ

feature flag 基盤のアーキテクチャ

上図が newmo における feature flag 基盤の全体像です。

まず Toggle Configuration は flagd の JSON Schema に従う JSON 形式です。ここには「宣言的定義」のセクションで述べる newmo 独自スキーマを flagd スキーマへ変換した結果が格納されます。

次に OpenFeature Provider Layer です。flagd の In-Process Resolver (In-Process Evaluation Mode) には、Toggle Configuration をポーリングしてメモリに保持する sync provider という内部コンポーネントが存在します。公式にもいくつか実装が提供されていますが、newmo の Modular Monolith では 1  つの sync provider が複数の GCS  バケットを定期的に監視する必要があるため、自前で sync provider を実装しました。flagd 用の OpenFeature Provider 自体は既に OSS として公開されているため、In-Process Evaluation Mode で起動しつつ自作 sync provider を DI するだけで簡単に Provider を構成できます。

OpenFeature Client Layer では、feature flag のカテゴリ判定に応じた処理、内部トークンから Evaluation Context (Toggle Context) を生成する処理、OpenFeature Client のラップによる BooleanValue/StringValue で値取得、さらに Trace/Metrics/Log を Datadog へ送信する処理を担います。

Toggle Router Layer は「自動生成」のセクションで詳述するように、独自 Toggle Configuration から自動生成された薄いラッパーです。内部的には前述の OpenFeature Client Layer のメソッドを呼び出すだけのシンプルな構造になっています。

Toggle Point Layer は各 component の初期化やビジネスロジックが記述される場所で、新機能のリリースやカナリアリリースを行いたい箇所に Toggle Point を挿入します。

mobile / web などクライアントサイド、あるいは別プロセスから feature flag を参照したい場合も、同じ基盤にアクセスします。OpenFeature Client Layer を薄くラップした featureflag component がその責務を担い、GraphQL や gRPC 経由でフラグ評価を提供します。各言語ごとに独自の OpenFeature Provider を用意し、同様に自動生成された Toggle Router を組み込んでいるため、ここで説明した構造がそれぞれのランタイムに内包されます。

宣言的定義

newmo ではモノレポを採用しており、mobile/web/server/IaC など、すべてのソースコードを Git で一元管理しています。feature flag についても同じ思想を適用し、「Git を見れば現在すべての環境の Toggle Configuration が把握できる」状態を目指しました。そのため Toggle Configuration は YAML で宣言し、ソースコードとともにバージョン管理しています。

featureflags:
  - name: "some-feature"
    release:
      targeting:
        preset: FEATURE_FLAG_TARGET_PRESET_ALL_END_USER
      variant:
        local: true
        dev: true
        stg: true
        prod: false

上記は some-feature という Release Toggles を定義する例です。targeting.preset は評価対象を示すプリセットで、FEATURE_FLAG_TARGET_PRESET_ALL_END_USER は「すべてのエンドユーザー」を表します。このほかにも「内部ユーザーのみ」「環境ごとに指定 ID のユーザー」「特定属性を持つユーザー」など、さまざまなプリセットを用意しており、必要に応じて自由に拡張できます。

variant セクションでは環境ごとの評価結果を宣言できます。例では ローカル・開発・ステージング環境 では常に true、本番環境 では false を返すよう設定しています。コードを変更せず YAML を更新するだけでロールアウト戦略を切り替えられるため、レビューやロールバックの運用コストを最小化できます。

なお Static Toggles と Experiment Toggles も同様に Protocol Buffers で型安全なスキーマを定義しており、開発者は YAML で直感的にフラグを追加・編集できます。

自動生成

上で定義した Toggle Configuration を元に、

  • flagd の Toggle Configuration
  • 各言語向けの Toggle Router
  • 各言語向けのテストヘルパー

を自動生成しています。

some-feature を例にすると、flagd 側の Toggle Configuration は次のようになります。

{
  "$schema": "https://flagd.dev/schema/v0/flags.json",
  "flags": {
    "<component name>.some-feature": {
      "state": "ENABLED",
      "variants": {
        "off": false,
        "on": true
      },
      "defaultVariant": "off",
      "targeting": {
        "if": [
          {
            "and": [true]
          },
          "on",
          "off"
        ]
      },
      "metadata": {
        "category": "release"
      }
    }
  }
}

YAML で宣言した内容がそのまま JSON に変換されています。targeting を見ると「常に "on" (=true)」と評価されることがわかります。targeting.preset に複雑なプリセットを指定した場合でも、JsonLogic へ自動的に変換されるため柔軟なルール生成が可能です。

Go 向けの Toggle Router は次のように自動生成されます。

// Code generated by xxx; DO NOT EDIT.

package featureflag

import (
    "context"

    "github.com/newmo/.../featureflag"
)

const component = "<component name>"

type ReleaseToggle interface {
    SomeFeature(ctx context.Context) bool
}

type releaseToggle struct {
    client featureflag.Client
}

func NewReleaseToggle(client featureflag.Client) ReleaseToggle {
    return &releaseToggle{client: client}
}

func (r *releaseToggle) SomeFeature(ctx context.Context) bool {
    flagName := "<component name>.some-feature"

    v, err := r.client.BooleanValue(ctx, flagName, false)
    if err != nil {
        return false
    }
    return v
}

フラグ名を UpperCamelCase にしたメソッドを自動生成しています。フラグが増えてもメソッドも増えるため、フラグ名の typo が原理的に発生しません。静的解析もしやすく、後述するフラグ使用回数の集計にも役立ちます。また Static Toggles や Experiment Toggles のように評価値が string 型になる場合も、定義された variant 一覧 から enum を自動生成して型安全に扱えます。

テストヘルパーは以下のように生成されます。

// Code generated by xxx; DO NOT EDIT.

package featureflagtest

import (
    "context"

    "github.com/newmo/.../fakefeatureflag"
)

const component = "<component name>"

type FlagSetter struct {
    client *fakefeatureflag.Client
}

func NewFlagSetter(client *fakefeatureflag.Client) *FlagSetter {
    return &FlagSetter{client: client}
}

func (f *FlagSetter) SetSomeFeature(ctx context.Context, value any) error {
    flagName := "<component name>.some-feature"

    err := f.client.SetFlag(ctx, flagName, value)
    if err != nil {
        return err
    }
    return nil
}

fakefeatureflag はメモリ上にフラグ値を保持するフェイククライアントです。テスト前に任意の値をセットできるため、分岐ごとのカバレッジを高めやすくなります。

これらのコードは Go だけでなく、feature flag を利用する他言語向けにも同様に自動生成されます。

fire and forget

“fire and forget” とは、本来は呼び出し側が非同期リクエストを送ったあと、レスポンスを待たずに後続処理を進める設計を指します。newmo の feature flag 基盤でも同じ発想を採り、「一度定義して利用したら、役目を終えたフラグは自動で消えてくれる」状態を目標にしています。

開発者が YAML で宣言した Toggle Configuration は、main へのマージをトリガーに GitHub Actions が自動で flagd 用 JSON に変換し、GCS へアップロードします。逆に Toggle Point (フラグ呼び出し箇所) が 0 になった feature flag は CI によって検出され、同じく CI 上でクリーンアップされます。

定期クリーンアップの Slack 通知

ここで注意すべきは、Toggle Configuration の同期タイミングと Toggle Router のデプロイタイミングが必ずしも一致しない点です。Configuration はすべての環境で main へマージされた瞬間に反映されますが、Router (=アプリケーションのデプロイ) は環境ごとにタイムラグが生じる可能性があります (例えば、リリースするブランチが各環境で異なる運用など)。したがって 真に削除してよいフラグ は、「各環境で稼働しているすべてのバージョンのソースコードで Toggle Point が 0 になった場合」に限られます。

このあたりの詳細は、弊社 Iwamin (@B_Sardine) さんによる発表資料が詳しいのでぜひ参照してください  *10。

Toggle Point の検出は次の方法で実装しています。Go コードは AST を用いた静的解析で確実に呼び出しを捉え、ほかの言語は暫定的に正規表現で候補を grep しています。Toggle Router をコード生成しているため、メソッド名を検索すればよいという点でも自動生成の恩恵を受けています。

補足
正規表現での検索は偽陽性が発生しやすいため、本来は tree‑sitter などのパーサを使うのが望ましいです。tree‑sitter には WASM  バインディングがあり、Go であれば wazero を用いて実行することも可能かもしれませんね。

今後の展望

現状では Toggle Point の削除作業を Product Team に全面的に委ねているため、削除漏れが発生すると Zombie Flag が溜まっていきます。このままでは Zombie Flag に対する本質的な解決には至りません。今後は、よりプロアクティブなアプローチを検討していきたいと考えています。

さらに、feature flag 基盤は高い信頼性を担保できなければなりません。評価ロジックにバグや障害があると、Toggle Router が誤った値を返して一般ユーザーに実験機能を公開してしまうなど、重大なインシデントへ直結します。現在はリクエスト数が多くないため In-Process Evaluation mode で運用できていますが、事業がスケールした際には可用性と一貫性を両立させる設計が不可欠です。将来的にはリモート評価クラスタの導入やキャッシュ戦略の最適化、マルチゾーン冗長化などを視野に入れ、基盤全体の耐障害性とパフォーマンスを強化していくことも考えられるかもしれません。

さいごに

長文にお付き合いいただき、ありがとうございました。feature flag は Gartner の Hype Cycle に 2023,2024 年で “slope of enlightenment” に位置付けられ *11、CNCF では OpenFeature が Incubating Project に選定されるなど、世界的にさらに注目されている技術領域となっています。一方で国内の具体的な活用事例はまだまだ少なく、導入を躊躇っている開発チームも多いのが実情だと思います。本記事が、トランクベース開発と feature flag を取り入れて開発者体験を向上させたい皆さまの一助になれば幸いです。

最後に、Flagsmith の Ben Rometsch 氏が OpenFeature 公式ブログに寄稿した記事 *12 から印象的な一節を引用します。

“Introducing feature flags will fundamentally change the way you build software, so pulling in the right stakeholders from the beginning is important. Start by creating a working group with representatives from your engineering, QA, product, and DevOps/infrastructure teams. Additionally, you’ll need full executive buy-in to bring flags on at scale. This is true from a budget perspective but, just as importantly, from a time perspective, too. Part of the reason the eBay team was so successful with their OpenFeature adoption is because they were given full license to use engineering hours to get it done.”

feature flag の導入はソフトウェア開発の在り方を根本的に変えるため、初期段階から関係者をしっかり巻き込むことが重要です。まずはエンジニア、QA、プロダクト、DevOps/インフラの代表者でワーキンググループを組織しましょう。さらに、大規模運用には予算面だけでなく工数確保の観点でも経営層の全面的な理解と協力が欠かせません。eBay チームが OpenFeature を成功させた要因の一つは、エンジニアリング時間を自由に投入できた環境があったからです。

newmo では、CI/CD の自動化基盤、認証認可とコンポーネント間伝播の仕組み、OpenTelemetry/Datadog によるオブザーバビリティ、自動生成ツールなどの 既存の Platform があったからこそ、その上に feature flag 基盤を構築することができています。また重要なのは、技術を導入して終わりにするのではなく、継続的な開発への投資と改善を惜しまない姿勢 です。Platform は一朝一夕で完成するものではなく、育て、使い込み、磨きをかけ続けることで、はじめて組織とプロダクトの進化を支える強力なドライバーとなります。

これからも各チームが挑戦の旗を掲げ、高い生産性を維持しながら活躍し続けられるよう、Platform Team は Platform Engineering を推進していきます。

書いた人: tobi