フィーチャートグル(別名フィーチャーフラグ)

フィーチャートグル(フィーチャーフラグとも呼ばれる)は強力な手法であり、コードを変更せずにシステムの動作を変更できます。さまざまな使用カテゴリに分類され、トグルの実装と管理においてその分類を考慮することが重要です。トグルは複雑さを導入します。スマートなトグル実装手法と適切なツールを使用してトグル設定を管理することで、その複雑さを抑制できますが、システム内のトグル数を制限することも目指すべきです。

2017年10月9日


Photo of Pete Hodgson

ピート・ホジソンは、美しく雨の多い太平洋岸北西部を拠点とする独立系ソフトウェアデリバリーコンサルタントです。独立系ソフトウェアデリバリーコンサルタント。スタートアップのエンジニアリングチームのエンジニアリングプラクティスとテクニカルアーキテクチャの改善を支援することに特化しています。

ピートは以前、Thoughtworksで6年間コンサルタントとして勤務し、西海岸事業のテクニカルプラクティスをリードしていました。また、サンフランシスコのさまざまなスタートアップでテクニカルリードを務めていました。


「フィーチャートグリング」とは、チームが新しい機能を迅速かつ安全にユーザーに提供するのに役立つ一連のパターンです。フィーチャートグリングに関するこの記事では、まず、フィーチャートグルが役立つ典型的なシナリオを示す短いストーリーから始めます。その後、詳細に掘り下げ、チームがフィーチャートグルで成功するのに役立つ具体的なパターンとプラクティスについて説明します。

フィーチャートグルは、フィーチャーフラグ、フィーチャービット、フィーチャーフリッパーとも呼ばれます。これらはすべて、同じ一連の手法の同義語です。この記事では、フィーチャートグルとフィーチャーフラグを交換可能に使用します。

トグルの物語

場面を想像してください。洗練された都市計画シミュレーションゲームに取り組んでいる複数のチームのうちの1つのチームに所属しているとします。あなたのチームは、コアシミュレーションエンジンを担当しています。スプラインレチキュレーションアルゴリズムの効率を高めるというタスクを割り当てられました。これは実装の大幅な見直しが必要であり、数週間かかることがわかっています。一方、チームの他のメンバーは、コードベースの関連領域で進行中の作業を続ける必要があります。

過去の経験から、長期間にわたるブランチのマージは非常に困難であるため、この作業のためにブランチを作成することは避けたいと考えています。代わりに、チーム全体がtrunkで作業を続けることを決定しますが、スプラインレチキュレーションの改善に取り組んでいる開発者は、フィーチャートグルを使用して、作業がチームの残りの部分に影響を与えたり、コードベースを不安定にしたりしないようにします。

フィーチャーフラグの誕生

アルゴリズムに取り組んでいるペアによって導入された最初の変更を示します。

変更前

  function reticulateSplines(){
    // current implementation lives here
  }

これらの例はすべてJavaScript ES2015を使用しています。

変更後

  function reticulateSplines(){
    var useNewAlgorithm = false;
    // useNewAlgorithm = true; // UNCOMMENT IF YOU ARE WORKING ON THE NEW SR ALGORITHM
  
    if( useNewAlgorithm ){
      return enhancedSplineReticulation();
    }else{
      return oldFashionedSplineReticulation();
    }
  }
  
  function oldFashionedSplineReticulation(){
    // current implementation lives here
  }
  
  function enhancedSplineReticulation(){
    // TODO: implement better SR algorithm
  }

ペアは現在のアルゴリズムの実装を`oldFashionedSplineReticulation`関数に移動し、`reticulateSplines`を**トグルポイント**にしました。これで、新しいアルゴリズムに取り組んでいる人がいれば、`useNewAlgorithm = true`行のコメントを外して「新しいアルゴリズムを使用する」**機能**を有効にできます。

フラグを動的にする

数時間後、ペアは新しいアルゴリズムをシミュレーションエンジンの統合テストの一部で実行する準備ができました。また、同じ統合テスト実行で古いアルゴリズムも実行したいと考えています。そのためには、機能を動的に有効または無効にする必要があります。つまり、`useNewAlgorithm = true`行のコメントのオンオフというぎこちないメカニズムから先に進む時が来ました。

function reticulateSplines(){
  if( featureIsEnabled("use-new-SR-algorithm") ){
    return enhancedSplineReticulation();
  }else{
    return oldFashionedSplineReticulation();
  }
}

これで、`featureIsEnabled`関数、**トグルルーター**を導入しました。これは、どのコードパスがライブであるかを動的に制御するために使用できます。トグルルーターを実装するには、単純なインメモリストアから高度な分散システムと洗練されたUIまで、多くの方法があります。ここでは、非常に単純なシステムから始めます。

function createToggleRouter(featureConfig){
  return {
    setFeature(featureName,isEnabled){
      featureConfig[featureName] = isEnabled;
    },
    featureIsEnabled(featureName){
      return featureConfig[featureName];
    }
  };
}

ES2015のメソッド短縮記法を使用していることに注意してください。

設定ファイルから読み込まれる可能性のあるデフォルト設定に基づいて新しいトグルルーターを作成できますが、機能を動的にオンまたはオフすることもできます。これにより、自動テストでトグルされた機能の両側を確認できます。

describe( 'spline reticulation', function(){
  let toggleRouter;
  let simulationEngine;

  beforeEach(function(){
    toggleRouter = createToggleRouter();
    simulationEngine = createSimulationEngine({toggleRouter:toggleRouter});
  });

  it('works correctly with old algorithm', function(){
    // Given
    toggleRouter.setFeature("use-new-SR-algorithm",false);

    // When
    const result = simulationEngine.doSomethingWhichInvolvesSplineReticulation();

    // Then
    verifySplineReticulation(result);
  });

  it('works correctly with new algorithm', function(){
    // Given
    toggleRouter.setFeature("use-new-SR-algorithm",true);

    // When
    const result = simulationEngine.doSomethingWhichInvolvesSplineReticulation();

    // Then
    verifySplineReticulation(result);
  });
});

リリースの準備

さらに時間が経過し、チームは新しいアルゴリズムが機能的に完成していると信じています。これを確認するために、機能オフと機能オンの両方でシステムを実行するように、より高レベルの自動テストを変更してきました。チームは、すべてが期待通りに動作することを確認するために、手動の探索的テストも行いたいと考えています。結局のところ、スプラインレチキュレーションはシステムの動作の重要な部分です。

まだ一般的に使用できる準備ができていない機能の手動テストを実行するには、本番環境で一般ユーザーに対して機能をオフにし、内部ユーザーに対して機能をオンにすることができる必要があります。この目標を達成するには、多くの異なるアプローチがあります。

  • **トグル設定**に基づいて意思決定を行うトグルルーターを用意し、その設定を環境固有のものにします。本番環境以外では新しい機能をオンにしないでください。
  • 何らかの管理UIを介して実行時にトグル設定を変更できるようにします。その管理UIを使用して、テスト環境で新しい機能をオンにします。
  • トグルルーターに、リクエストごとの動的なトグルに関する意思決定を行う方法を教えます。これらの決定は、特別なCookieやHTTPヘッダーを探すなど、**トグルコンテキスト**を考慮に入れます。通常、トグルコンテキストは、リクエストを行うユーザーを識別するためのプロキシとして使用されます。

(これらのアプローチについては後で詳しく説明しますので、これらの概念が初めてでも心配しないでください。)

チームは、柔軟性が高いことから、リクエストごとのトグルルーターを使用することにしました。チームは特に、これにより、別のテスト環境を用意しなくても新しいアルゴリズムをテストできることを高く評価しています。代わりに、本番環境でアルゴリズムをオンにできますが、(特別なCookieを介して検出される)内部ユーザーのみに限られます。チームは、自分自身に対してそのCookieをオンにして、新しい機能が期待通りに動作することを確認できます。

カナリアリリース

新しいスプラインレチキュレーションアルゴリズムは、これまでのところ探索的テストに基づいて良好に見えます。しかし、ゲームのシミュレーションエンジンの重要な部分であるため、すべてのユーザーに対してこの機能をオンにすることにまだためらいがあります。チームは、フィーチャーフラグインフラストラクチャを使用して**カナリアリリース**を実行し、ユーザーベースのほんの一部(「カナリア」コホート)に対してのみ新しい機能をオンにすることにしました。

チームは、ユーザーコホート(機能が常にオンまたはオフになっているユーザーグループ)の概念をトグルルーターに組み込みました。ユーザーIDの剰余などを使用して、ユーザーベースの1%のランダムサンプリングを介してカナリアユーザーのコホートが作成されます。このカナリアコホートは常に機能がオンになり、ユーザーベースの残りの99%は古いアルゴリズムを引き続き使用します。新しいアルゴリズムがユーザーの行動に悪影響を与えないことを確信するために、両方のグループの主要なビジネス指標(ユーザーエンゲージメント、獲得総収益など)を監視します。チームが新しい機能に悪影響がないことを確信したら、トグル設定を変更して、すべてのユーザーベースに対してオンにします。

A/Bテスト

チームのプロダクトマネージャーはこのアプローチについて学び、非常に興奮しています。彼女は、チームが同様のメカニズムを使用してA/Bテストを実行することを提案しています。犯罪率アルゴリズムを変更して汚染レベルを考慮に入れると、ゲームのプレイアビリティが増加するか減少するかは、長年にわたって議論されてきました。彼らは現在、データを使用して議論を解決する能力を持っています。このアイデアの本質を捉えた安価な実装を計画し、フィーチャーフラグで制御します。彼らは、かなりの数のユーザーに対して機能をオンにし、コントロールコホートと比較してそれらのユーザーの行動を調べます。HiPPOではなく、データに基づいて物議を醸している製品に関する議論を解決できます。

この短いシナリオは、フィーチャートグリングの基本的な概念を示すとともに、このコア機能が持つことができるさまざまなアプリケーションを強調することを目的としています。これらのアプリケーションの例をいくつか見たので、もう少し深く掘り下げてみましょう。さまざまなカテゴリのトグルを探り、それらを異なるものにする要因について見ていきます。保守可能なトグルコードの書き方について説明し、最後に、フィーチャートグルされたシステムの落とし穴を回避するためのプラクティスを共有します。

トグルのカテゴリ

フィーチャートグルによって提供される基本的な機能、つまり、1つのデプロイ可能なユニット内に代替のコードパスを出荷し、実行時にそれらの間で選択できる機能を確認しました。上記のシナリオはまた、この機能がさまざまなコンテキストでさまざまな方法で使用できることを示しています。すべてのフィーチャートグルを同じバケットに入れるのは魅力的かもしれませんが、これは危険な道です。さまざまなカテゴリのトグルに作用する設計上の力はかなり異なっており、すべてを同じ方法で管理すると、将来問題が発生する可能性があります。

フィーチャートグルは、フィーチャートグルの存続期間と、トグリングの決定がどれだけ動的であるかという2つの主要な次元で分類できます。考慮すべき他の要因もあります(たとえば、誰がフィーチャートグルを管理するかなど)が、存続期間と動的性を、トグルの管理方法をガイドするのに役立つ2つの大きな要因と考えています。

これらの2つの次元を通してさまざまなカテゴリのトグルを検討し、それらがどこに適合するかを見てみましょう。

リリース用トグル

リリース用トグルにより、不完全でテストされていないコードパスを、オンにならない可能性のある潜在的なコードとして本番環境にデプロイできます。

これらは、継続的デリバリーを実践しているチームのtrunkベース開発を可能にするフィーチャーフラグです。これにより、進行中の機能を共有統合ブランチ(例:masterまたはtrunk)にチェックインできますが、そのブランチをいつでも本番環境にデプロイできます。リリース用トグルでは、不完全でテストされていないコードパスを、潜在的なコードとして本番環境にデプロイできます。オンにならない可能性のある潜在的なコードです。

プロダクトマネージャーは、この同じアプローチのプロダクト中心バージョンを使用して、完成していない製品機能がエンドユーザーに公開されるのを防ぐこともできます。たとえば、eコマースサイトのプロダクトマネージャーは、特定の配送パートナーでのみ機能する新しい「予想配送日」機能をユーザーに表示したくない場合があります。すべての配送パートナーにその機能が実装されるまで待つことを優先するからです。プロダクトマネージャーには、機能が完全に実装されテスト済みであっても、機能を公開したくないその他の理由がある場合があります。たとえば、機能リリースがマーケティングキャンペーンと調整されている可能性があります。このようにリリース トグルを使用することは、「[機能]のリリースと[コード]のデプロイメントを分離する」という継続的デリバリーの原則を実装する最も一般的な方法です。

リリース トグルは本質的に移行的なものです。一般的に、1週間から2週間以上は残しておくべきではありませんが、プロダクト中心のトグルは、より長く残しておく必要がある場合があります。リリース トグルのトグルの決定は、通常非常に静的です。特定のリリースバージョンのすべてのトグルの決定は同じであり、トグル構成の変更を含む新しいリリースをロールアウトすることでそのトグルの決定を変更することは、通常完全に許容されます。

実験用トグル

実験トグルは、多変量またはA/Bテストを実行するために使用されます。システムの各ユーザーはコホートに配置され、実行時にトグルルーターは、どのコホートにいるかに基づいて、常に特定のユーザーを一方のコードパスまたはもう一方のコードパスに送信します。さまざまなコホートの集計された動作を追跡することで、eコマースシステムの購入フローやボタンの行動喚起の言葉遣いなど、さまざまなコードパスの影響を比較できます。

実験トグルは、統計的に有意な結果を生成するのに十分な時間、同じ構成で残しておく必要があります。トラフィックパターンによっては、数時間から数週間の寿命になる可能性があります。システムへのその他の変更によって実験の結果が無効になるリスクがあるため、それ以上長くすることは役に立ちません。本質的に、実験トグルは非常に動的です。受信する各リクエストは、異なるユーザーを代理している可能性が高いため、最後とは異なるルーティングが行われる可能性があります。

運用用トグル

これらのフラグは、システムの動作の運用上の側面を制御するために使用されます。パフォーマンスへの影響が不明な新しい機能をロールアウトする場合、システムオペレーターが必要に応じて本番環境でその機能を迅速に無効化または低下させることができるように、運用トグルを導入する場合があります。

ほとんどの運用トグルは比較的短命です。新しい機能の運用上の側面に対する信頼が得られたら、フラグを廃止する必要があります。ただし、システムには少数の長寿命の「キルスイッチ」が存在することが珍しくありません。これにより、本番環境のオペレーターは、システムが異常に高い負荷を受けている場合に、重要なシステム機能を適切に低下させることができます。たとえば、負荷が高い場合は、生成に比較的コストのかかるホームページの推奨パネルを無効にしたい場合があります。私は、高需要製品の発売直前に、ウェブサイトの主要な購入フローの多くの非クリティカルな機能を意図的に無効にすることができる運用トグルを維持していたオンライン小売業者に相談しました。これらの種類の長寿命の運用トグルは、手動で管理されるサーキットブレーカーと見なすことができます。

すでに述べたように、これらのフラグの多くは短時間しか存在しませんが、オペレーターのためにほぼ無期限に配置される可能性のあるいくつかの重要な制御が存在する可能性があります。これらのフラグの目的は、オペレーターが本番環境の問題に迅速に対処できるようにすることであるため、非常に迅速に再構成する必要があります。運用トグルを切り替えるために新しいリリースをロールアウトする必要があると、運用担当者は不満を感じる可能性が高いです。

権限付与用トグル

内部ユーザーのセットに対して新しい機能をオンにすることは、シャンパンブランチです。自分のシャンパンを飲むための早期の機会です。

これらのフラグは、特定のユーザーが受信する機能または製品エクスペリエンスを変更するために使用されます。たとえば、有料顧客のみにオンにする「プレミアム」機能のセットがある場合があります。あるいは、内部ユーザーのみに利用可能な「アルファ」機能のセットと、内部ユーザーとベータユーザーの両方に利用可能な別の「ベータ」機能のセットがある場合もあります。内部ユーザーまたはベータユーザーのセットに対して新しい機能をオンにするこの手法を、シャンパンブランチと呼びます。「自分のシャンパンを飲む」ための早期の機会です。

シャンパンブランチは、カナリアリリースと多くの点で似ています。両者の違いは、カナリアリリースされた機能はランダムに選択されたユーザーのコホートに公開されるのに対し、シャンパンブランチ機能は特定のユーザーセットに公開されることです。

プレミアムユーザーのみに公開される機能を管理する方法として使用する場合、パーミッショントグルは、他のカテゴリの機能トグルと比較して、非常に長寿命になる可能性があります(数年規模)。パーミッションはユーザー固有であるため、パーミッショントグルのトグルの決定は常にリクエストごとに実行され、これは非常に動的なトグルになります。

さまざまなカテゴリのトグルの管理

これで、トグルの分類スキームができたので、動的性と寿命の2つの次元が、さまざまなカテゴリの機能フラグの扱い方にどのように影響するかについて説明できます。

静的トグルと動的トグル

実行時ルーティングの決定を行っているトグルには、より高度なトグルルーターと、それらのルーターのより複雑な構成が必要です。

単純な静的ルーティングの決定の場合、トグル構成は、その静的なオン/オフ状態をトグルポイントに中継する役割を担うトグルルーターごとに機能ごとに単純なオンまたはオフにすることができます。前述のように、他のカテゴリのトグルはより動的であり、より高度なトグルルーターを必要とします。たとえば、実験トグルのルーターは、ユーザーのIDに基づく何らかの整合性のあるコホート化アルゴリズムを使用して、特定のユーザーに対して動的にルーティング決定を行います。構成から静的なトグル状態を読み取るのではなく、このトグルルーターは、実験コホートとコントロールコホートのサイズなど、コホート構成を読み取る必要があります。その構成は、コホート化アルゴリズムへの入力として使用されます。

このトグル構成を管理するさまざまな方法については、後で詳しく説明します。

長期間のトグルと一時的なトグル

トグルカテゴリを、本質的に一時的なものと、長寿命であり数年間にわたって存在する可能性のあるものの2つに分類することもできます。この区別は、機能のトグルポイントを実装する方法に大きな影響を与える必要があります。数日後に削除されるリリース トグルを追加する場合、トグルルーターで単純なif/elseチェックを行うトグルポイントで済む可能性があります。これは、以前のスパイン網目化の例で使用した方法です。

function reticulateSplines(){
  if( featureIsEnabled("use-new-SR-algorithm") ){
    return enhancedSplineReticulation();
  }else{
    return oldFashionedSplineReticulation();
  }
}

ただし、非常に長い間残ると予想されるトグルポイントを含む新しいパーミッショントグルを作成する場合は、無差別にif/elseチェックを散りばめてそれらのトグルポイントを実装したくありません。より保守可能な実装技術を使用する必要があります。

実装手法

機能フラグは、かなり混乱したトグルポイントコードを生み出す傾向があり、これらのトグルポイントはコードベース全体に広がる傾向もあります。コードベースの機能フラグについては、この傾向を抑制することが重要であり、フラグが長寿命の場合は非常に重要です。この問題を軽減するのに役立つ実装パターンとプラクティスがいくつかあります。

意思決定ポイントと意思決定ロジックのデカップリング

機能トグルでよくある間違いの1つは、トグルの決定が行われる場所(トグルポイント)と、その決定の背後にあるロジック(トグルルーター)を結合することです。例を見てみましょう。次世代のeコマースシステムに取り組んでいます。新しい機能の1つは、ユーザーが注文確認メール(別名請求書メール)内のリンクをクリックすることで、注文を簡単にキャンセルできるようにすることです。機能フラグを使用して、すべての次世代機能のロールアウトを管理しています。最初の機能フラグの実装は次のようになります。

invoiceEmailer.js

  const features = fetchFeatureTogglesFromSomewhere();

  function generateInvoiceEmail(){
    const baseEmail = buildEmailForInvoice(this.invoice);
    if( features.isEnabled("next-gen-ecomm") ){ 
      return addOrderCancellationContentToEmail(baseEmail);
    }else{
      return baseEmail;
    }
  }

請求書メールを生成する際に、InvoiceEmaillerはnext-gen-ecomm機能が有効になっているかどうかを確認します。有効な場合は、メール送信者がメールに追加の注文キャンセルコンテンツを追加します。

これは妥当なアプローチのように見えますが、非常に脆いです。請求書メールに注文キャンセル機能を含めるかどうかという決定は、かなり広範なnext-gen-ecomm機能に直接関連付けられています。少なくともマジック文字列を使用しています。請求書メール送信コードは、注文キャンセルコンテンツが次世代機能セットの一部であることを知る必要があるのはなぜでしょうか?次世代機能の一部を公開せずに注文キャンセルを公開したい場合はどうなりますか?その逆はどうでしょうか?特定のユーザーのみに注文キャンセルをロールアウトしたい場合はどうなりますか?機能が開発されるにつれて、このような「トグルスコープ」の変更が発生することは非常に一般的です。また、これらのトグルポイントはコードベース全体に広がる傾向があることを覚えておいてください。現在の方法では、トグルの決定ロジックがトグルポイントの一部であるため、その決定ロジックの変更には、コードベース全体に広がっているすべてのトグルポイントを調べる必要があります。

幸いなことに、ソフトウェアのあらゆる問題は、間接層を追加することで解決できます。トグルの決定ポイントと、その決定の背後にあるロジックを次のように切り離すことができます。

featureDecisions.js

  function createFeatureDecisions(features){
    return {
      includeOrderCancellationInEmail(){
        return features.isEnabled("next-gen-ecomm");
      }
      // ... additional decision functions also live here ...
    };
  }

invoiceEmailer.js

  const features = fetchFeatureTogglesFromSomewhere();
  const featureDecisions = createFeatureDecisions(features);

  function generateInvoiceEmail(){
    const baseEmail = buildEmailForInvoice(this.invoice);
    if( featureDecisions.includeOrderCancellationInEmail() ){
      return addOrderCancellationContentToEmail(baseEmail);
    }else{
      return baseEmail;
    }
  }

FeatureDecisionsオブジェクトを導入しました。これは、フィーチャートグルの決定ロジックを集約する役割を果たします。コード内の各特定のトグル決定に対して、このオブジェクトに決定メソッドを作成します。この場合、「請求書メールに注文キャンセル機能を含めるべきか」は、includeOrderCancellationInEmail決定メソッドで表されます。現時点では、決定「ロジック」はnext-gen-ecomm機能の状態をチェックする単純なパススルーですが、このロジックが進化するにつれて、それを管理する単一の場所ができました。特定のトグル決定のロジックを変更したい場合は、いつでも単一の場所を参照すれば済みます。決定の範囲を変更することもできます。たとえば、どの特定のフィーチャーフラグが決定を制御するかなどです。あるいは、決定の理由を変更する必要があるかもしれません。静的なトグル構成によって制御されることから、A/Bテストによって制御されるか、注文キャンセルインフラストラクチャの一部の中断などの運用上の懸念事項によって制御されるように変更する必要があるかもしれません。いずれの場合も、請求書メール送信プログラムは、そのトグル決定がどのように、なぜ行われているかについて、全く意識する必要がありません。

意思決定の反転

以前の例では、請求書メール送信プログラムは、フィーチャーフラッグインフラストラクチャにどのように動作すべきかを問い合わせる役割を担っていました。これは、請求書メール送信プログラムが、フィーチャーフラッグという追加の概念を認識する必要があり、それに対応する追加のモジュールと結合されていることを意味します。これにより、請求書メール送信プログラムの扱いと単体での考慮が難しくなり、テストも難しくなります。フィーチャーフラッグは、システム全体で時間とともにますます普及する傾向があるため、ますます多くのモジュールがグローバルな依存関係としてフィーチャーフラッグシステムと結合されるようになります。理想的なシナリオではありません。

ソフトウェア設計では、多くの場合、制御の反転を適用することで、このような結合の問題を解決できます。この場合も同様です。以下は、請求書メール送信プログラムをフィーチャーフラッグインフラストラクチャから切り離す方法です。

invoiceEmailer.js

  function createInvoiceEmailler(config){
    return {
      generateInvoiceEmail(){
        const baseEmail = buildEmailForInvoice(this.invoice);
        if( config.includeOrderCancellationInEmail ){
          return addOrderCancellationContentToEmail(email);
        }else{
          return baseEmail;
        }
      },
  
      // ... other invoice emailer methods ...
    };
  }

featureAwareFactory.js

  function createFeatureAwareFactoryBasedOn(featureDecisions){
    return {
      invoiceEmailler(){
        return createInvoiceEmailler({
          includeOrderCancellationInEmail: featureDecisions.includeOrderCancellationInEmail()
        });
      },
  
      // ... other factory methods ...
    };
  }

ここで、InvoiceEmaillerFeatureDecisionsにアクセスするのではなく、構成オブジェクトconfigを介して構築時にこれらの決定が注入されます。InvoiceEmaillerは、フィーチャーフラッグについて全く知識を持ちません。実行時に動作のいくつかの側面を構成できることを知っているだけです。これにより、InvoiceEmaillerの動作のテストも容易になります。テスト中に異なる構成オプションを渡すだけで、注文キャンセルコンテンツの有無にかかわらずメールを生成する方法をテストできます。

describe( 'invoice emailling', function(){
  it( 'includes order cancellation content when configured to do so', function(){
    // Given 
    const emailler = createInvoiceEmailler({includeOrderCancellationInEmail:true});

    // When
    const email = emailler.generateInvoiceEmail();

    // Then
    verifyEmailContainsOrderCancellationContent(email);
  };

  it( 'does not includes order cancellation content when configured to not do so', function(){
    // Given 
    const emailler = createInvoiceEmailler({includeOrderCancellationInEmail:false});

    // When
    const email = emailler.generateInvoiceEmail();

    // Then
    verifyEmailDoesNotContainOrderCancellationContent(email);
  };
});

また、これらの決定注入オブジェクトの作成を集中化するために、FeatureAwareFactoryを導入しました。これは、一般的な依存性注入パターンの適用です。コードベースにDIシステムが導入されている場合は、おそらくそのシステムを使用してこのアプローチを実装するでしょう。

条件分岐の回避

これまでの例では、トグルポイントはif文を使用して実装されていました。これは、シンプルで短命なトグルには理にかなっているかもしれません。しかし、複数のトグルポイントが必要なフィーチャー、またはトグルポイントが長期間存在すると予想される場所では、ポイント条件分岐はお勧めしません。より保守可能な代替手段は、何らかの戦略パターンを使用して代替コードパスを実装することです。

invoiceEmailler.js

  function createInvoiceEmailler(additionalContentEnhancer){
    return {
      generateInvoiceEmail(){
        const baseEmail = buildEmailForInvoice(this.invoice);
        return additionalContentEnhancer(baseEmail);
      },
      // ... other invoice emailer methods ...
  
    };
  }

featureAwareFactory.js

  function identityFn(x){ return x; }
  
  function createFeatureAwareFactoryBasedOn(featureDecisions){
    return {
      invoiceEmailler(){
        if( featureDecisions.includeOrderCancellationInEmail() ){
          return createInvoiceEmailler(addOrderCancellationContentToEmail);
        }else{
          return createInvoiceEmailler(identityFn);
        }
      },
  
      // ... other factory methods ...
    };
  }

ここでは、請求書メール送信プログラムをコンテンツ拡張関数で構成できるようにすることで、戦略パターンを適用しています。FeatureAwareFactoryは、FeatureDecisionによってガイドされ、請求書メール送信プログラムの作成時に戦略を選択します。注文キャンセルをメールに含める必要がある場合、そのコンテンツをメールに追加するエンハンサー関数を渡します。そうでない場合は、identityFnエンハンサー(効果がなく、変更を加えずにメールをそのまま返すエンハンサー)を渡します。

トグル設定

動的ルーティングと動的設定

先に、フィーチャーフラグを、特定のコードデプロイメントに対してトグルルーティングの決定が本質的に静的なものと、実行時に動的に決定が変化するものに分けました。フラッグの決定が実行時に変化する方法は2つあります。第一に、運用トグルのようなものは、システム障害に対応してオンからオフに動的に再構成される可能性があります。第二に、パーミッショントグルや実験トグルなどのトグルのカテゴリでは、リクエストを行っているユーザーなど、リクエストコンテキストに基づいて、各リクエストに対して動的なルーティング決定を行います。前者は再構成によって動的であるのに対し、後者は本質的に動的です。これらの本質的に動的なトグルは、非常に動的な**決定**を行う可能性がありますが、**構成**は静的なままであり、再デプロイによってのみ変更できる可能性があります。実験トグルはこのタイプのフィーチャーフラグの例です。実行時に実験のパラメーターを変更する必要はありません。実際、そうすると実験の統計的有意性が失われる可能性があります。

静的設定を優先する

フィーチャーフラグの性質が許すのであれば、ソースコントロールと再デプロイメントによるトグル構成の管理が好ましいです。ソースコントロールによるトグル構成の管理は、インフラストラクチャ・コードなどに対してソースコントロールを使用することと同じメリットをもたらします。トグル構成をトグルされるコードベースと一緒に配置することができ、大きな利点があります。トグル構成は、コード変更やインフラストラクチャ変更とまったく同じ方法で継続的デリバリーパイプラインを通過します。これにより、CDのすべてのメリット、つまり環境全体で一貫した方法で検証される繰り返し可能なビルドが可能になります。また、フィーチャーフラグのテスト負担を大幅に軽減します。トグルのオンとオフの両方でリリースのパフォーマンスを確認する必要性が少なくなるからです。なぜなら、その状態はリリースに組み込まれており、変更されないからです(少なくともあまり動的ではないフラッグの場合)。ソースコントロールにトグル構成を並べて配置するもう1つの利点は、以前のリリースでのトグルの状態を容易に確認でき、必要に応じて以前のリリースを容易に再現できることです。

トグル設定を管理するためのアプローチ

静的構成が好ましい一方で、運用トグルなど、より動的なアプローチが必要なケースもあります。シンプルだがあまり動的ではないアプローチから、非常に洗練されているが追加の複雑さが伴うアプローチまで、トグル構成の管理のためのいくつかのオプションを見てみましょう。

ハードコーディングされたトグル設定

最も基本的な手法(フィーチャーフラッグとはみなされないほど基本的なものかもしれません)は、コードのブロックを単純にコメント化またはコメント解除することです。例えば

function reticulateSplines(){
  //return oldFashionedSplineReticulation();
  return enhancedSplineReticulation();
}

コメント化アプローチよりも少し洗練されているのは、プリプロセッサの#ifdef機能の使用です(利用可能な場合)。

このタイプのハードコーディングではトグルの動的な再構成が許可されないため、フラッグを再構成するためにコードをデプロイするというパターンに従うことをいとわないフィーチャーフラグにのみ適しています。

パラメーター化されたトグル設定

ハードコードされた構成によって提供されるビルド時構成は、多くのテストシナリオを含む多くのユースケースには柔軟性がありません。アプリやサービスを再ビルドすることなくフィーチャーフラグを再構成できる、少なくとも簡単なアプローチは、コマンドライン引数または環境変数を介してトグル構成を指定することです。これは、この手法がフィーチャートグルまたはフィーチャーフラッグと呼ばれていたずっと前から存在する、シンプルで古くからあるトグルのアプローチです。ただし、制限もあります。多数のプロセス全体で構成を調整することが煩雑になる可能性があり、トグルの構成の変更には、再デプロイまたは少なくともプロセスの再起動(そしておそらくトグルを再構成する担当者によるサーバーへの特権アクセスも)が必要です。

トグル設定ファイル

もう1つのオプションは、何らかの構造化ファイルからトグル構成を読み取ることです。このトグル構成アプローチが、より一般的なアプリケーション構成ファイルの一部として開始するのは非常に一般的です。

トグル構成ファイルを使用すると、アプリケーションコード自体を再ビルドしなくても、フィーチャーフラグを単に変更することで再構成できます。ただし、ほとんどの場合、アプリを再ビルドする必要はありませんが、フラッグを再構成するには再デプロイを実行する必要があるでしょう。

アプリケーションDB内のトグル設定

特定の規模に達すると、静的ファイルを使用してトグル構成を管理することが煩雑になる可能性があります。ファイルによる構成の変更は比較的面倒です。サーバー群全体での整合性を確保することは課題であり、変更を一貫して行うことはさらに困難になります。これに対応して、多くの組織はトグル構成を何らかの集中化されたストア(多くの場合、既存のアプリケーションDB)に移行します。これは通常、システムオペレーター、テスター、プロダクトマネージャーがフィーチャーフラッグとその構成を表示および変更できる何らかの管理UIの構築を伴います。

分散トグル設定

システムアーキテクチャに既に含まれている汎用DBを使用してトグル構成を格納することは非常に一般的です。フィーチャーフラッグが導入され、普及し始めると、これは当然の場所です。しかし、今日では、アプリケーション構成の管理に適した特殊な階層型キーバリューストア(Zookeeper、etcd、Consulなどのサービス)が登場しています。これらのサービスは分散クラスタを形成し、クラスタに接続されたすべてのノードに対して環境構成の共有ソースを提供します。構成は必要に応じて動的に変更でき、クラスタ内のすべてのノードは変更を自動的に通知されます。これは非常に便利な追加機能です。これらのシステムを使用してトグル構成を管理することで、フリート全体で調整されたトグル構成に基づいて決定を行うトグルルーターをフリートの各ノードに配置できます。

これらのシステム(Consulなど)の一部には、トグル構成を管理するための基本的な方法を提供する管理UIが付属しています。しかし、ある時点で、トグル構成を管理するための小さなカスタムアプリが通常作成されます。

設定のオーバーライド

これまでの議論では、すべての構成が単一のメカニズムによって提供されていると仮定していました。多くのシステムの現実には、さまざまなソースからの構成のオーバーライドレイヤーがより洗練されています。トグル構成では、デフォルト構成と環境固有のオーバーライドを組み合わせることが一般的です。これらのオーバーライドは、追加の構成ファイルほど単純なものから、Zookeeperクラスタのような洗練されたものまで、さまざまなものから取得できます。環境固有のオーバーライドは、デリバリーパイプライン全体でまったく同じビットと構成が流れるという継続的デリバリーの理想に反することを認識しておく必要があります。多くの場合、実際的な理由から環境固有のオーバーライドが使用されますが、デプロイ可能なユニットと構成の両方を環境に依存しないものにすることで、よりシンプルで安全なパイプラインが実現します。フィーチャートグルされたシステムのテストについて説明するときに、このトピックを改めて説明します。

リクエストごとのオーバーライド

環境固有の構成オーバーライドに対する代替アプローチは、特別なCookie、クエリパラメーター、またはHTTPヘッダーを使用して、リクエストごとにトグルのオン/オフ状態をオーバーライドできるようにすることです。これには、完全な構成オーバーライドよりもいくつかの利点があります。サービスがロードバランシングされている場合、どのサービスインスタンスにアクセスしているかに関係なく、オーバーライドが確実に適用されることを確認できます。本番環境でフィーチャーフラグをオーバーライドして他のユーザーに影響を与えることなく、また誤ってオーバーライドを残しておく可能性も低くなります。リクエストごとのオーバーライドメカニズムが永続的なCookieを使用している場合、システムをテストする担当者は、ブラウザに一貫して適用される独自のトグルオーバーライドのカスタムセットを構成できます。

このリクエストごとのアプローチの欠点は、好奇心旺盛なエンドユーザーまたは悪意のあるエンドユーザーがフィーチャートグルの状態自体を変更するリスクが導入されることです。一部の組織は、リリースされていないフィーチャーが十分に意図的な当事者にとって公開されている可能性があるという考えに抵抗するかもしれません。オーバーライド構成に暗号署名を行うことは、この懸念を軽減するための1つのオプションですが、いずれにしても、このアプローチはフィーチャートグルシステムの複雑さと攻撃対象領域を増大させます。

Cookieベースのオーバーライドに関するこの手法については、この投稿で詳しく説明しており、私とThoughtworksの同僚がオープンソース化したRuby実装についても説明しています

フィーチャートグル付きシステムでの作業

フィーチャートグルは非常に役立つ手法ですが、追加の複雑さも持ち込んでいます。フィーチャーフラッグ付きシステムで作業を容易にするためのいくつかの手法があります。

現在のフィーチャートグル設定を公開する

デプロイされたアーティファクトにビルド番号/バージョン番号を埋め込み、そのメタデータを何らかの方法で公開して、開発者、テスター、運用担当者が特定の環境で実行されているコードを特定できるようにすることは、常に役立つプラクティスです。この考え方はフィーチャフラグにも適用する必要があります。フィーチャフラグを使用するシステムは、運用担当者がトグル構成の現在の状態を検出する方法を提供する必要があります。HTTP指向のSOAシステムでは、これは多くの場合、何らかのメタデータAPIエンドポイントによって実現されます。例えば、Spring BootのActuatorエンドポイントを参照してください。

構造化されたトグル設定ファイルを活用する

基本的なトグル構成を、ソース管理によって管理される何らかの構造化された、人間が読み取れるファイル(多くの場合YAML形式)に格納するのが一般的です。このファイルから得られる追加の利点もあります。各トグルの人間が読み取れる説明を含めることは、特にコアデリバリーチーム以外の担当者が管理するトグルにとって非常に役立ちます。運用停止中にOpsトグルを有効にするかどうかを判断しようとしたときに、どちらを見たいと思いますか? `basic-rec-algo` それとも `"単純なレコメンデーションアルゴリズムを使用します。これは高速でバックエンドシステムへの負荷が少ないですが、標準アルゴリズムよりもはるかに精度が低くなります。"`?一部のチームでは、作成日、主要な開発者連絡先、または短命のトグルの有効期限など、トグル構成ファイルに追加のメタデータを含めることも選択しています。

さまざまなトグルを異なる方法で管理する

前述のように、フィーチャトグルにはさまざまな種類があり、それぞれ特性が異なります。これらの違いを受け入れ、すべてのトグルを同じ技術的手段で制御する場合でも、異なるトグルを異なる方法で管理する必要があります。

ホームページに「おすすめ商品」セクションがあるECサイトの以前の例を再検討しましょう。最初は、開発中のこのセクションをリリース用トグルの背後に配置したかもしれません。その後、収益増加に役立っていることを検証するために、実験用トグルの背後に移動したかもしれません。最後に、極端な負荷がかかっているときにオフにできるように、Opsトグルの背後に移動したかもしれません。トグルポイントから意思決定ロジックを切り離すという上記のアドバイスに従っていれば、トグルカテゴリの違いは、トグルポイントのコードにはまったく影響を与えていません。

しかし、フィーチャフラグ管理の観点からは、これらの移行は絶対に影響を与えるはずです。リリース用トグルから実験用トグルへの移行の一環として、トグルの構成方法は変更され、おそらく異なる領域(ソース管理のyamlファイルではなく管理UIなど)に移動します。開発者ではなく、製品担当者が構成を管理するようになります。同様に、実験用トグルからOpsトグルへの移行は、トグルの構成方法、構成が存在する場所、構成を管理する担当者という点で別の変更を意味します。

フィーチャートグルは検証の複雑さを導入する

フィーチャフラグ付きのシステムでは、特にテストに関して、継続的デリバリープロセスがより複雑になります。CDパイプラインを介して移動するにつれて、同じアーティファクトに対して複数のコードパスをテストする必要があることがよくあります。その理由を説明するために、トグルがオンの場合に新しい最適化された税計算アルゴリズムを使用するか、そうでない場合は既存のアルゴリズムを引き続き使用できるシステムをリリースしていると想像してください。特定のデプロイ可能なアーティファクトがCDパイプラインを移動している時点では、トグルが本番環境でオンまたはオフにされるかどうかはわかりません。それがフィーチャフラグのポイントです。したがって、本番環境でライブになる可能性のあるすべてのコードパスを検証するには、アーティファクトを **両方** の状態でテストする必要があります。トグルがオンとオフの両方の場合です。

1つのトグルを使用している場合、少なくともテストの一部を倍増する必要があることがわかります。複数のトグルを使用している場合、考えられるトグルの状態の組み合わせが爆発的に増加します。これらの状態ごとに動作を検証することは、途方もない作業です。これにより、テストに重点を置く担当者からフィーチャフラグに対する健全な懐疑論が生じる可能性があります。

幸いなことに、状況は一部のテスターが最初に想像するほど悪くありません。フィーチャフラグ付きのリリース候補は、いくつかのトグル構成でテストする必要がありますが、*すべての*可能な組み合わせをテストする必要はありません。ほとんどのフィーチャフラグは互いに影響せず、ほとんどのリリースでは1つ以上のフィーチャフラグの構成に変更は加えられません。

良い慣習としては、フィーチャフラグがオフの場合に既存の動作またはレガシー動作を有効にし、オンの場合に新しい動作または将来の動作を有効にすることです。

では、チームはどのフィーチャトグル構成をテストする必要がありますか?本番環境でライブになると予想されるトグル構成、つまり現在の本番トグル構成と、オンに切り替える予定のトグルをテストすることが最も重要です。リリース予定のトグルがオフになっているフォールバック構成もテストすることをお勧めします。将来のリリースにおける予期しない回帰を回避するために、多くのチームはすべてのトグルがオンになっている状態でいくつかのテストを実行します。このアドバイスは、フィーチャがオフの場合に既存の動作またはレガシー動作が有効になり、フィーチャがオンの場合に新しい動作または将来の動作が有効になるというトグルのセマンティクスの規約に固執する場合にのみ意味があります。

フィーチャフラグシステムがランタイム構成をサポートしていない場合は、トグルを切り替えるためにテスト中のプロセスを再起動する必要がある場合があり、最悪の場合、アーティファクトをテスト環境に再デプロイする必要がある場合があります。これは、検証プロセスのサイクルタイムに非常に悪影響を与え、CI/CDが提供する重要なフィードバックループに影響を与えます。この問題を回避するために、フィーチャフラグの動的なインメモリ再構成を可能にするエンドポイントを公開することを検討してください。実験用トグルなど、トグルの両方のパスを実行するのがさらに難しい場合、この種のオーバーライドはさらに必要になります。

特定のサービスインスタンスを動的に再構成するこの機能は、非常に強力なツールです。不適切に使用すると、共有環境で多くの痛みと混乱を引き起こす可能性があります。この機能は、自動テストでのみ、および手動の探索的テストとデバッグの一部としてのみ使用する必要があります。本番環境で使用するためのより汎用的なトグル制御メカニズムが必要な場合は、上記のトグル構成セクションで説明したように、実際の分散構成システムを使用して構築するのが最善です。

トグルの配置場所

エッジでのトグル

リクエストごとのコンテキストが必要なトグルカテゴリ(実験用トグル、パーミッショントグル)の場合、システムのエッジサービス(つまり、エンドユーザーに機能を提供する公開されているWebアプリ)にトグルポイントを配置することが理にかなっています。これは、ユーザーの個々のリクエストが最初にドメインに入る場所であり、したがってトグルルーターがユーザーとそのリクエストに基づいてトグルに関する意思決定を行うために利用できるコンテキストが最も多い場所です。システムのエッジにトグルポイントを配置することの副次的な利点は、システムの中核から面倒な条件付きトグルロジックを排除することです。多くの場合、このRailsの例のように、HTMLをレンダリングしているまさにその場所にトグルポイントを配置できます。

someFile.erb

  <%= if featureDecisions.showRecommendationsSection? %>
    <%= render 'recommendations_section' %>
  <% end %>

エッジにトグルポイントを配置するのは、まだリリースの準備ができていない新しいユーザー向けの機能へのアクセスを制御する場合にも理にかなっています。このコンテキストでは、UI要素を表示または非表示にするトグルを使用して、アクセスを制御できます。たとえば、Facebookを使用してアプリケーションにログインする機能を構築していますが、まだユーザーにロールアウトする準備ができていないとします。この機能の実装には、アーキテクチャのさまざまな部分への変更が含まれる可能性がありますが、「Facebookでログイン」ボタンを非表示にするUIレイヤーの単純なフィーチャトグルを使用して、機能の露出を制御できます。

これらのタイプのフィーチャフラグの中には、未リリースの機能の大部分が実際に公開されている可能性がありますが、ユーザーが検出できないURLに存在していることに注目することが興味深いです。

コアでのトグル

アーキテクチャのより深い場所に配置する必要がある、より低レベルの他のタイプのトグルがあります。これらのトグルは通常技術的なものであり、内部的に機能がどのように実装されるかを制御します。例としては、サードパーティAPIの前に新しいキャッシングインフラストラクチャを使用するか、またはリクエストを直接そのAPIにルーティングするかを制御するリリース用トグルがあります。これらの場合、トグルに関する意思決定を、機能がトグルされているサービス内にローカライズすることが唯一の賢明な選択肢です。

フィーチャートグルの維持コストの管理

フィーチャフラグは、特に最初に導入されたときに、急速に増加する傾向があります。それらは作成が簡単で安価であるため、多くの場合、多くが作成されます。ただし、トグルには維持コストがかかります。コードに新しい抽象化または条件付きロジックを導入する必要があります。また、大きなテスト負担も発生します。Knight Capital Groupの4億6000万ドルのミスは、フィーチャフラグを正しく管理しない場合に何が起こる可能性があるかについての警告となります(他のことの中でも)。

賢明なチームは、フィーチャトグルを維持コストがかかる在庫と見なし、その在庫を可能な限り少なくしようとします。

賢明なチームは、コードベース内のフィーチャトグルを維持コストがかかる在庫と見なし、その在庫を可能な限り少なくしようとします。フィーチャフラグの数を管理可能に保つために、チームは不要になったフィーチャフラグを積極的に削除する必要があります。一部のチームでは、リリース用トグルが最初に導入されるたびに、常にトグル削除タスクをチームのバックログに追加するというルールがあります。他のチームは、トグルに「有効期限」を設定しています。さらに進んで、「タイムボム」を作成するチームもあります。これは、有効期限が過ぎてもフィーチャフラグが残っている場合、テストに失敗するか(またはアプリケーションの起動を拒否することさえ)します。在庫削減にはリーンのアプローチも適用でき、システムが一度に持つことができるフィーチャフラグの数に制限を設けることができます。その制限に達したら、新しいトグルを追加したい場合は、最初に既存のフラグを削除する作業を行う必要があります。


謝辞

Brandon Byars氏とMax Lincoln氏に、この記事の初期ドラフトに対する詳細なフィードバックと提案を提供してくれたことに感謝します。Martin Fowler氏にサポート、アドバイス、励ましをいただいたことに感謝します。Michael Wongwaisayawan氏とLeo Shaw氏に編集レビューをしていただき、Fernanda Alcocer氏に私の図をより見やすくしてくれたことに感謝します。

重要な改訂

2017年10月9日: フィーチャフラグを同義語として強調

2016年2月8日: 最終部分:記事の完全版を公開

2016年2月5日: 第7部:トグル付きシステムでの作業

2016年2月2日: 第6部:トグル構成

2016年1月28日: 第5部:実装技術

2016年1月27日: 第4部:さまざまなカテゴリのトグルの管理

2016年1月22日: 第3部:Opsとパーミッショントグル

2016年1月21日: 第2回 - リリースと実験トグル

2016年1月19日: 第1回を公開 - トグルの物語