Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

FlipperのフィーチャーフラグでRailsに新機能を安全にデプロイする方法

こんなシナリオを想像してみてください。あなたはRails開発者で、ここ数日かけて全員が待ち望んでいる素晴らしい機能を開発していました。規模は大きく複雑でしたが、厳密なテストを経ているので、すべてが期待どおりに動作すると確信しています。納期もあるため、そのままデプロイ。すると、瞬く間に大混乱に陥ります。

新機能が原因で、一部のユーザーに対してアプリ全体が動かなくなってしまったのです。理由もよく分かりません。テスト中にはバグは一切出ていなかったのに。変更をロールバックしても、被害はすでに出ています。顧客は不満を抱き、あなたは当面の間、ダメージコントロールと原因調査に追われることになるでしょう。

問題の本質は、どれだけテストを重ねても、複雑な変更のリリースには本質的なリスクが伴うという点です。この機能を段階的にロールアウトできていたら——たとえば最初はごく一部のユーザーだけに公開していたら、よかったのにと思ったはずです。さらに言えば、最初の問題が発生した時点ですぐに機能をオフにできれば、なおさら良かったでしょう。

まさにそこで活躍するのがフィーチャーフラグです。この記事では、Railsにおけるフィーチャーフラグの仕組みから、Flipper gemを使った実装方法まで詳しく解説します。まずは「フィーチャーフラグとは何か」から見ていきましょう。

Ruby on Railsにおけるフィーチャーフラグとは?

フィーチャーフラグとは、アプリケーションを実行したまま特定の機能を有効・無効に切り替える仕組みのことです。フラグが有効ならその機能が使われ、無効なら使われません。擬似コードで表すと、次のようなイメージです。

if Flipper.enabled?(:my_awesome_feature)
  # 新機能のコード
else
  # 従来の動作
end

「フィーチャーフラグ」という名前は少し誤解を招きます。ランタイムでアプリケーションの挙動を制御できるのは「機能」に限らず、プログラム内のあらゆる処理に適用できます。たとえばパフォーマンスチューニングの検証やA/Bテストにも活用できます。フィーチャーフラグと普通の条件分岐との大きな違いは、実行時に変更できるという点です。

説明のため、環境変数を使ったシンプルなフィーチャーフラグの仕組みを想像してみましょう。

# 環境変数 MY_AWESOME_FEATURE=true のときだけ新機能を有効化
if ENV.fetch("MY_AWESOME_FEATURE", "false") == "true"
  enable_my_awesome_feature
end

環境変数の値を書き換えるだけで、アプリの一部を自由にオン・オフできます。どの部分を? 好きな部分です!

機能が無効なときにUIの一部を隠すこともできますし、最新のリファクタリングに自信がない場合にコードの一部を切り替えることも可能です。個人的に気に入っている使い方のひとつが、ルーティング制約を使ってルートごと隠してしまう方法です。

# config/routes.rb
constraints ->(request) { Flipper.enabled?(:my_awesome_feature) } do
  get "/my-awesome-feature", to: "my_awesome_feature#index"
end
# コントローラ側でも念のためガードしておくと安心
class MyAwesomeFeatureController < ApplicationController
  before_action :check_feature_flag

  private

  def check_feature_flag
    head :not_found unless Flipper.enabled?(:my_awesome_feature)
  end
end

このように、フィーチャーフラグには多くの用途があることが分かります。ただし、この単純な実装には大きな弱点があります。機能が全ユーザーに対して一律にオンかオフになってしまう点です。

特定のユーザーやユーザーグループにだけ機能を有効にしたい、あるいは時間の一部だけで有効にしたい場合はどうすればいいのでしょうか? そこで登場するのがFlipperです。

Flipper gemでフィーチャーフラグを使う

Flipperは、Railsでフィーチャーフラグとその切り替え手段を提供してくれるgemです。非常にモジュール化されていて、本体のgemに加えてストレージアダプタを選ぶ必要があります。詳細は後述するとして、ここではActiveRecordアダプタを使ってみましょう。

Gemfileに以下の行を追加して、bundle installを実行してください。

gem 'flipper'
gem 'flipper-active_record'

ActiveRecordアダプタを使う場合、Flipperはフィーチャーフラグをデータベースに保存するため、新しいテーブルの作成が必要です。Flipperには必要なマイグレーションを生成してくれる便利なジェネレータが付属しています。

rails g flipper:active_record
rails db:migrate

これで、先ほどの単純な実装と同じようにFlipperを使えます。

Flipper.enable(:my_awesome_feature)   # 有効化
Flipper.disable(:my_awesome_feature)  # 無効化

Flipper.enabled?(:my_awesome_feature) # 状態確認

大きな違いは、環境変数を書き換える代わりに、Flipperの管理画面(UI)から機能を切り替えられるようになる点です。Flipper UIのgemを追加してインストールしましょう。

gem 'flipper-ui'

ルーティングファイルを編集して、Flipper UIをマウントします。

# config/routes.rb
mount Flipper::UI.app => '/flipper'

アプリケーションを起動してFlipper UIにアクセスすると、利用可能なすべての機能とその状態の一覧が表示されます。さっそくmy_awesome_featureという機能を作成してみましょう。

FlipperのフィーチャーフラグでRailsに新機能を安全にデプロイする方法

Flipperのフィーチャートグル

お気づきかもしれませんが、Flipper UIでは単純な有効・無効の切り替え以外にも多くのことができます。Flipperでは、一部のユーザーにだけ機能を有効にしたり、一定の割合の時間だけ有効にしたりすることも可能です。フィーチャーフラグの設定には、5つの異なるメカニズムを使えます。

ひとつはブール値(boolean)トグルで、これはグローバルなオン・オフスイッチです。残りの4つはもっと興味深いものです。グループ(groups)、アクター(actors)、アクターの割合(percentage of actors)、そして時間の割合(percentage of time)です。

グループ(Groups)

特定のユーザーグループにだけ機能を有効にしたい場合は、グループトグルを使います。まだ存在しない場合は、config/initializers/flipper.rbを作成し、以下のコードで新しいグループ(たとえば有料ユーザー)を登録しましょう。

# config/initializers/flipper.rb
Flipper.register(:paid_users) do |actor|
  actor.respond_to?(:paid?) && actor.paid?
end

この新しいトグルを使うには、Flipperの呼び出し時にユーザー(ドキュメントでは「アクター」と呼ばれます)と機能名を渡すように変更します。

Flipper.enabled?(:my_awesome_feature, current_user)

Flipperはイニシャライザで定義したブロックを使って、そのユーザーに対して機能が有効かどうかを判定します。この例ではpaid?メソッドが呼び出されます。あとはFlipper UIでこのグループを使えます。「Add a Group」ボタンをクリックして、登録したグループを選択してください。

FlipperのフィーチャーフラグでRailsに新機能を安全にデプロイする方法

グループは有料ユーザーである必要はありません。チームのメンバーでもいいですし、ベータプログラムに登録したユーザーでも構いません。グループトグルは、一部のユーザーにだけ何かをリリースしたい場合に役立ちます。ベータリリースや社内リリースなどを想像してみてください。

アクター(Actors)

場合によっては、特定の個人にだけリリースしたいこともあるでしょう。そんなときはアクタートグルが選択肢になります。

このメカニズムを使うためにイニシャライザを変更する必要はありません。ただし、アクターとなるモデル(多くの場合はUserモデル)がflipper_idを実装している必要があります。Flipper::Identifierをincludeすればそれを簡単に満たせます。

class User < ApplicationRecord
  include Flipper::Identifier
end
# 個別のユーザーに対して機能を有効化
Flipper.enable_actor(:my_awesome_feature, User.find(params[:user_id]))

このIDを使って、個々のユーザーに対して機能を有効にできます。

FlipperのフィーチャーフラグでRailsに新機能を安全にデプロイする方法

このトグルは、特定の人にだけ機能をプレビューさせたい場合に便利です。たとえば、一般公開前にパートナー向けのテストアカウントだけ機能へアクセスできるようにする、といった使い方が考えられます。

アクターの割合(Percentage of Actors)

機能の利用を少しずつ拡大していきたいなら、アクターの割合トグルが最適です。パフォーマンス最適化を実装したものの、本番環境でどう振る舞うか確信が持てない状況を想像してください。まず少数のユーザーにロールアウトし、アプリケーションを監視しながら徐々に割合を上げていく、という進め方ができます。

FlipperのフィーチャーフラグでRailsに新機能を安全にデプロイする方法

万が一問題が起きても、割合をゼロに戻せばすぐに影響を止められます。

時間の割合(Percentage of Time)

最後に紹介するのは、リクエストの一定割合に対して機能を有効にする方法です。このトグルを使う場合、アクターを渡す必要はありません。

# リクエストごとにランダムに判定される
Flipper.enabled?(:my_awesome_feature)

私は2つの異なる実装のパフォーマンスを比較するときによく使っています。ただし注意点として、このトグルはランダム判定です。同じユーザーでもリクエストごとに異なる挙動になります。

Flipperの高度な設定

これまで、ActiveRecordアダプタを使ってフィーチャーフラグの情報をデータベースに保存する方法を見てきました。しかし、要件によっては別のアダプタを使いたい場合もあるでしょう。

ほとんどの場合、ストレージアダプタを入れ替えるには、対応するgemをインストールしてFlipperの設定を更新するだけです。たとえばRedisアダプタを使いたい場合は、flipper-redis gemをGemfileに追加し、Flipperのイニシャライザを更新します。

gem 'flipper-redis'
# config/initializers/flipper.rb
require 'flipper-redis'

Flipper.configure do |config|
  config.default do
    Flipper.new(Flipper::Adapters::Redis.new(Redis.new))
  end
end

ストレージアダプタの設定以外にも、Flipperには調整できる要素がたくさんあります。

フィーチャーフラグの追加にはわずかなパフォーマンスコストが伴うため、通常はメモ化やキャッシュを組み合わせるのがおすすめです。また、Flipper UIへのアクセスを認証済みユーザーに限定したいケースも多いはずです。これらのトピックは本記事の範囲外ですが、より深く知りたい方はFlipperの公式ドキュメントを参照することをおすすめします。

Railsでフィーチャーフラグを使う際の注意点

フィーチャーフラグはとても便利ですが、他の多くの技術と同じようにトレードオフがあります。

フィーチャーフラグの追加と維持には組織的なオーバーヘッドが伴います。フラグを使って隠れた機能をリリース・ロールアウトするのは、「通常の」コードをデプロイするほど単純ではありません。もちろん、忘れずにフラグをオンにしなければなりません! それはそれほど難しくありませんが、不要になったフィーチャーフラグのクリーンアップは意外と手間かもしれません。

コードにフィーチャーフラグを追加するたびに条件分岐がひとつ増えます。注意していないと、コードはあっという間に条件分岐だらけになってしまいます。いつ・なぜ特定のフィーチャーフラグを作成したのかを把握し続けるのは難しく、残念ながらFlipper UI自体にはフラグを管理するための高度な機能はあまり用意されていません。

また、フィーチャーフラグの使用には軽微なパフォーマンスペナルティが発生することも認識しておきましょう。緩和策はありますが、Flipperを誤って設定したり誤用したりすると、体感できるレベルの影響が出る可能性があります。

まとめ:Ruby on Railsでフィーチャーフラグを始めよう

この記事では、フィーチャーフラグがより自信を持ったリリースにどう役立つかを学び、その基本的な仕組みを理解し、Flipper gemを使った始め方を確認しました。

フィーチャーフラグが活躍する場面は数多くあります。ユースケースに応じて、5つのトグル——ブール値、グループ、アクター、アクターの割合、リクエストの割合——のいずれかを選びましょう。

フィーチャーフラグの扱いには注意点があり、慣れるまで少し時間がかかるかもしれません。それでも、開発者の武器として持っておく価値のある優れたツールです。

それではまた次回、Happy Coding!

P.S. Ruby Magicの記事を公開後すぐに読みたい方は、Ruby Magicニュースレターを購読して、記事を見逃さないようにしましょう!

  1. Rackとは?Ruby開発者のための基本仕組みと自作ミドルウェア入門

    RailsやSinatraなど、あらゆるRuby製Webフレームワークの裏側では、何が起こっているのでしょうか? その答えがRackです。フレームワークとサーバーをつなぐ、まさに要となる存在です。 では、Rackとは一体何なのでしょうか? Rackは、Webフレームワーク(Railsなど)とアプリケーションサーバー(Pumaなど)の間に位置する「層」です。 両者が正しくやり取りできるようにする、糊(グルー)のような役割を担っています。 なぜRackを使うのか? Rackを使う最大の理由は、異なるフレームワークとサーバーを自由に組み合わせられるようにするためです。 それぞれが差し替え可能なプラ

  2. AppSignalがAlpine Linux ARMを正式サポート、サーバー監視をさらに安心に

    本日、Alpine Linuxが動作するARMマシン向けのサポートを開始しました。この機能は、RubyおよびElixirのユーザー向けに提供されます。将来的には、Node.jsパッケージにもAlpine Linux ARMのサポートを追加したいと考えています。 ARM CPUアーキテクチャは、開発マシンから本番サーバーまで、その存在感をますます高めています。こうした流れを受け、AppSignalではサポート対象OSのリストにARMを追加することにしました。 インストール手順 パッケージのインストール方法は従来と変わりません。ARMマシンは自動的に検出され、お使いのシステムに適した拡張機能とエー