Feature Flagが拓く開発の新潮流 2

Feature Flagを支える「4つの構成要素」と「用途別4分類」の実践ポイント

第2回の今回は、Feature Flagを支える「4つの構成要素」と「用途別4分類」のポイントについて解説します。

石飛 真哉(いしとび しんや)

11月27日 6:30

はじめに

前回は、Feature Flagを「変数によってシステムの振る舞いを制御する仕組み」として定義し、その価値や代表的なユースケースを整理しました。デプロイとリリースを切り離し、ユーザー単位で柔軟に機能を出し分けられることが、現代のソフトウェア開発を大きく前進させるということを確認しました。

では、Feature Flagは実際にどのような仕組みで成り立っているのでしょうか。ひと口にフラグと言っても「未完成機能を隠すためのもの」と「有料ユーザーだけに機能を開放するもの」では求められる役割、ライフサイクルや寿命がまったく異なります。また、内部の構成要素を理解せずに使い始めるとチーム内での認識がばらばらになったり、コードベース上に多くの似通ったフラグの利用箇所が散在しかねません。

本稿では、Feature Flagの世界をもう一歩深く掘り下げます。

  • フラグを評価する際に関わる構成要素
    Toggle Point / Toggle Router / Toggle Context / Toggle Configuration
  • 用途に応じた4つの分類
    Release Toggle / Experiment Toggle / Ops Toggle / Permission Toggle

この2つのトピックを取り上げ、それぞれがどのように実際の開発現場で利用されているのかを整理します。これらを理解することで、単なる「便利なif文」ではなく、設計・運用上の意図を持って使い分けられる仕組みとしてFeature Flagを捉えられるようになります。

Feature Flag内部の構成要素

まずは、前回と同様にFeature Flagを実際に利用した簡単なGoのコード例を見てみましょう。

if client.BooleanValue(ctx, "use-new-algorithm") {
   processWithNewAlgorithm(ctx)
} else {
   processWithLegacyAlgorithm(ctx)
}

ここでのclientはFeature Flagのクライアントで、外部に存在するフラグの設定情報を取得するためのインターフェースです。BooleanValueclientのメソッドで、use-new-algorithmというフラグの値を何らかの評価方法によって真偽値に変換する役割を担っています。

一見すると単純なif文にしか見えませんが、この1行の裏側では複数の構成要素が連携して「誰に対して」「どの条件のときに」「どの値を返す」べきかを決めています。以降ではこの簡単な例から出発し、フラグの値がどのような仕組みで決定されるのか、その内部構造を整理していきます。

ここでは、以下の4つの構成要素を取り上げます。

  • Toggle Point
  • Toggle Router
  • Toggle Context
  • Toggle Configuration

Toggle Point

Toggle Pointは、フラグの評価結果を実際の挙動に反映させる「コード上の分岐点」です。上記のコード例ではif client.BooleanValue(ctx, "use-new-algorithm")の部分がToggle Pointに該当します。

もしuse-new-algorithmtrueと評価された場合はprocessWithNewAlgorithm、つまり新しいアルゴリズムで実行され、falseと評価された場合はprocessWithLegacyAlgorithm、つまり従来のアルゴリズムが実行されます。

Toggle Pointはあくまで「結果を使う側」であり、「どう評価するか」のロジックは別の場所(Toggle Routerなど)に切り出されている、というのがポイントです。

Toggle Router

Toggle Routerは、Toggle Pointからの問い合わせに対して一貫した評価結果を返す「フラグ評価の単一の情報源(single source of truth)」です。 上記のコード例では、client.BooleanValueメソッドがToggle Routerに該当します。

client.BooleanValueが内部で何をしているのかを、少しだけ擬似コードで覗いてみましょう。ここでは説明のため、簡略化したイメージコードを示します。

// BooleanValue は Toggle Point から呼ばれるエントリポイントであり、このメソッド自体が「Toggle Router」として振る舞います。
// ここでは擬似的な実装として、
//   1. Toggle Context を組み立てる
//   2. Toggle Configuration(フラグ設定)を取得する
//   3. ContextとConfigurationを突き合わせて評価する
//   4. エラー時やマッチしなかった場合はデフォルト値でフォールバックする
func (c *Client) BooleanValue(ctx context.Context, flagKey string) bool {
    // 1. リクエストごとの Toggle Context を組み立てる
    //    - どのユーザーがリクエストしているか
    //    - どの環境(dev / stg / prod)なのか
    //    - どんな属性(ロール、課金プラン、ロケーションなど)を持っているか
    toggleCtx := c.buildToggleContext(ctx)
    
    // 2. 外部ストアから、このフラグの Toggle Configuration(設定)を取得する
    //    - フラグのデフォルト値
    //    - 「どの属性を持つユーザーには true / false を返すか」といったルール
    //    - ロールアウト割合(全体の 10% だけ有効にする、など)
    cfg, err := c.getFlagConfig(ctx, flagKey)
    if err != nil {
        // 設定が取得できなかった場合は、安全側の値でフォールバックします。
        // ここではシンプルさのために false に固定していますが、
        // 実際にはフラグごとに「安全なデフォルト値」を決めておくのが望ましいです。
        log.Printf("failed to load flag config: key=%s err=%v (fallback=false)", flagKey, err)
        return false
    }

    // 3. Configuration と Context を使ってフラグの値を評価する
    //    - 「beta ユーザーなら true」
    //    - 「残りはロールアウト割合に応じて true / false を振り分ける」
    value, matched := c.evaluateBooleanFlag(cfg, toggleCtx)
    if !matched {
        // いずれのルールにもマッチしなかった場合は、
        // Toggle Configuration に定義されているデフォルト値にフォールバックします。
        return cfg.DefaultValue
    }
    
    // 4. ルールにマッチして評価できた場合は、その結果をそのまま返す
    return value
}

buildToggleContext / getFlagConfig / evaluateBooleanFlagはここでは擬似的なメソッドですが、

  • buildToggleContext … Toggle Contextを組み立てる
  • getFlagConfig … 外部ストアからToggle Configurationを取得する
  • evaluateBooleanFlag … Toggle ContextとToggle Configurationを突き合わせて評価する

という責務の分解が見て取れると思います。

Toggle Routerは、Toggle Contextから得られる情報(ユーザーIDや属性、リクエストのメタデータなど)をもとに、Toggle Configurationで定義されたルールに従ってフラグの値を評価します。例えば「特定のユーザーグループにだけフラグを有効にする」といったルールが設定されている場合、Toggle Routerはそのルールを適用し、最終的な真偽値をToggle Pointに返します。このようにFeature Flagの構成要素の中でも中核に位置するのがToggle Routerです。

Toggle Context

Toggle Contextは、Toggle Routerがフラグを評価する際に考慮する文脈情報をまとめたものです。この文脈情報は、ビジネスロジックと密接に結びつきます。

先ほどのBooleanValueの例では、context.Contextを元にbuildToggleContextでToggle Contextを組み立てています。context.Context自体がそのままToggle Contextというわけではなく、「そこからユーザーIDやプランなどの情報を取り出し、フラグ評価用のEvaluation Contextとして再構成する」というイメージです。

Toggle Contextをどのように設計するかにより「どの軸で機能を出し分けできるか」が決まります。つまり、Toggle Contextはビジネス観点から見た設計上の重要なポイントとなります。

Toggle Configuration

Toggle Configurationは、フラグの評価ルールや設定情報を保持するコンポーネントです。実体としては、外部の設定ストア(データベース、設定ファイル、Feature Flagプロバイダなど)に永続化されていることがほとんどです。多くのFeature Flagプロバイダは、Web UIを通じてToggle Configurationを編集できます。

Toggle Configurationには、例えば次のような情報が含まれます。

  • フラグの名前(use-new-algorithmなど)
  • デフォルト評価値
  • ユーザー属性やセグメントに基づく評価ルール
  • ロールアウト割合(一定割合のユーザーだけ有効にする設定)

Toggle RouterはこのToggle Configurationを参照しながら、Toggle Contextから得られる情報を使ってフラグの値を評価します。Toggle Configuration側でルールを変更すれば、コードを変更・デプロイすることなく挙動を制御できるのです。

整理してみれば直感的な分解ではありますが、技術的負債の解消や運用設計を考えるうえで、これらの構成要素を意識的に切り分けておくことには大きな意味があります。例えば、Toggle Pointがコードベースに無秩序に散在していると、フラグの寿命が尽きたあとに不要な分岐を削除するのが難しくなります。また、Toggle Configurationが適切に管理されていなければ「誰が・いつ・どのフラグを変更したのか」が追えず、リリースや障害対応の責任範囲が不明瞭になってしまいます。

このような「増え続けるフラグ」や「役目を終えても残り続けるフラグ」が生み出す運用上の課題については、次回以降で詳しく取り上げていきます。

Feature Flagの代表的な分類

構成要素の話は「どう動いているか」という仕組みの話でした。続いて、ここからは「何のために使うか」という用途の観点から、Feature Flagを代表的な分類に分けて整理していきます。

未完成の機能を隠すためのフラグ、A/Bテストに使うフラグ、プラン別機能制御のフラグ、障害対応用のキルスイッチなど、Feature Flagにはさまざまな用途があります。これらを混同せずにしっかり理解した上で使い分けることは、設計上も運用上も重要です。

ここでは、Pete Hodgson氏が提唱する代表的な4つの分類を紹介します。

  • Release Toggle
  • Experiment Toggle
  • Ops Toggle
  • Permission Toggle

これらは、dynamismとlongevityという2つの軸で分類されます。

dynamismは「フラグ評価がどれだけ動的か」を示しています。例えば、ユーザー属性や利用状況に応じてリアルタイムに評価されるフラグは高いdynamismを持ちます。一方で、環境ごとに一括で設定されるようなフラグは低いdynamismとなります。

longevityは「フラグがどれだけ長く存在するか」を示しています。例えば、未完成機能を隠すためのフラグは機能が完成すれば不要になるため短いlongevityを持ちます。一方で、プラン別機能制御のフラグはプロダクトの寿命とともに長く存在し続けるため、長いlongevityとなります。

Rlease Toggle

Release Toggleはdynamismが低く、longevityが短いフラグの分類です。

主に未完成の機能を隠すために使われるフラグです。トランクベース開発において、未完成の機能を本番環境にデプロイしつつユーザーには見せないようにして「機能のリリースとデプロイを分離する」役割を果たします。

機能が十分に完成し、安定したらフラグを削除してコードベースから取り除きます。

Experiment Toggle

Experiment Toggleはdynamismが高く、longevityは比較的短いものから長いものまで様々なフラグの分類です。

主にカナリアリリースやA/Bテストのために使われるフラグです。ユーザー属性や利用状況に応じて動的に評価され、異なるバージョンの機能やUIを同時に提供し、その効果を比較・測定するための土台になります。

安定性や効果が確認できたら、フラグを削除してコードベースから取り除きます。

Ops Toggle

Ops Toggleはdynamismが比較的低く、longevityが長いフラグの分類です。

主に障害対応や運用上の制御のために使われるフラグです。システムの特定の機能を即座に有効化・無効化する「キルスイッチ」として利用され、障害発生時の影響範囲を最小限に抑える役割を果たします。

機能の安定性やパフォーマンスへの影響が把握できたら、フラグを削除してコードベースから取り除きます。場合によっては、恒久的な運用上の制御として残すこともあります。

Permission Toggle

Permission Toggleはdynamismが高く、longevityが長いフラグの分類です。

主にユーザーの属性や課金プランに基づき、機能や体験を差別化するために使われるフラグです。例えば、有料プランのユーザーにだけ特定の機能を提供する場合や、内部ユーザー向けにアルファ機能を提供する場合などに利用されます。

実質的に「権限システム」や「プロダクト構成」の一部とみなされ、プレミアム機能や特定のユーザーグループ向けの機能としてプロダクトの寿命にわたって長く存在し続けます。

ここまで見てきたように、これらの4つの分類は単なるラベリング以上の意味を持ちます。

  • どのくらい評価が動的なのか(dynamism)
  • どれくらいの期間コードベースに存在し続けるのか(longevity)
  • 誰が責任を持って管理すべきフラグなのか(開発チーム / プロダクトマネージャー / SREなど)

といった観点が、分類ごとに大きく異なってくるからです。

自分たちのプロダクトで使われているFeature Flagを眺めてみて、「これはRelease Toggleのつもりだったのに、実はPermission Toggle的に恒常運用されているのでは?」といった違和感がないかをチェックしてみると、隠れた技術的負債や運用上のリスクを洗い出すきっかけになるはずです。

おわりに

本稿では、前回の「Feature Flag とは何か?」という出発点から一歩踏み込み、

  • 「フラグがどう評価されているか」を支える内部の構成要素
  • 「フラグを何のために使うのか」という用途の違いから見た代表的な分類

という2つの軸でFeatute Flagの世界へDeep diveしました。

裏側には「どこで分岐するか」「どの文脈を使うか」「どんなルールを誰が変えるのか」という設計・運用があることを確認したので、前回と同じコード例であるclient.BooleanValue(...)でも今回は全く異なる視点から捉えられたと思います。

次回は、この考え方を具体的な実装へとつなげるために、Feature FlagのOSSとしてデファクトスタンダードであるOpenFeatureや、OpenFeature準拠のフラグ評価エンジンであるflagdを題材に、今回説明した概念がReal Worldでどのように表現されているのか、どのような世界を描いているのかを見ていきましょう。

【参考】

この記事をシェアしてください

人気記事トップ10

人気記事ランキングをもっと見る