メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックカンパニーのエンジニアブログです。読者に有用な情報発信ができるよう心がけたいので応援のほどよろしくお願いします。

Feature Toggleを用いたRailsアプリの継続的なリリースと要注意事項

はじめに

皆様こんにちは、サーバーサイドエンジニアの草分です。

突然ですが、開発者の皆様、実装したソースコードはこまめにリリースしていますか? 「大きい機能なので開発に時間がかかる」「障害が起きないよう念入りにテストする必要がある」などの理由で、Featureブランチのままコミットグラフが伸びに伸びたりしていませんか?

大きな機能を作ること自体は悪いことではありませんが、大きすぎるFeatureブランチは、本流ブランチとの挙動の乖離やコードの衝突が発生しやすく、レビューやマージに多大な苦労を伴います。

この記事では、この問題の解決策の1つとなる「Feature Toggle」を、Ruby on Railsにおける実装方法と共にご紹介します。 Feature Toggle自体は開発手法の一種であるため、言語/フレームワークを問わず広く活用されています。

Feature Toggleとは

  • 「機能が動作する/動作しない を切り替える」機構です。
  • ソースコードは存在しますが、トグルを「ON」にするまで機能が動作しないよう制御します。

Feature Toggle 動作イメージ

効果

Featureブランチの早期マージが可能となる

通常、作りかけの機能を本流ブランチにマージしてしまうと、ユーザーに未完成の機能を提供することになるため、問題になってしまいます。

そこでFeature Toggleを用いて、開発中の機能はトグル「OFF」の場合動作しないように実装します。そうすることで、開発中の機能をユーザー環境に影響を出さずにマージできるようになります。

本番環境でのテストが可能となる

本番環境では、開発環境では見つからなかった問題が多々発生します。 例えば、既存データに予期せぬレコードが入っていたり、アプリではない別のレイヤーでトラブルが起きたり...

Feature Toggleで社内向けのテストユーザーのみ機能を利用できるよう制御すれば、実際のユーザー環境にリリースする前に、テストユーザーで機能の動作確認ができるようになります。

Railsでの実装例

この記事では、Feature Toggle実装の助けとなってくれる「Flipper」 gemをご紹介します。 このgemはフラグ管理と分岐制御の仕組みを提供しています。

github.com

Feature Toggleは独自に実装することもできますが、このgemを使えばフラグの動的切り替えやフラグの管理画面などを比較的簡単に導入することができます。

Flipperの基本機能

以下のようにフラグがONかOFFかによって処理を分岐させることが可能になります。

if Flipper.enabled?(:search)
  search_hoge
else
  puts 'nothing...'
end

フラグの有効化のメソッドも提供されています。

Flipper.enable(:search)

フラグの保存先

フラグ状況の保存先はプラグインによって切り替えることができます。 ActiveRecord, Redisなどから好きな保存場所を選びましょう。

$ gem 'flipper-active_record'
$ rails g flipper:active_record

フラグ管理ページを作成するFlipper UI

画面からフラグを制御するflipper-ui gemも提供されています。 サービスの管理画面の1つとして組み込んでおけば運用が楽になるでしょう。

Flipper UI

また、当たり前の話ですが、管理ページは一般公開しないように実装しましょう。 参考: Flipper UI#security

しかしこのFlipper UIですが、ページ内に動画が表示されるfun modeが存在します。 開発者の遊び心かと思いますが、業務利用であれば必要ないのでOFFにしてしまいましょう。

Flipper::UI.configure do |config|
  config.fun = false
end

特定ユーザーのみの分岐

フラグON/OFFだけでなく、「特定ユーザーのみON」といった制御も可能です。 Flipperでは「Actor」という名前で機能が提供されています。 https://www.flippercloud.io/docs/features#enablement-actor

これにより、テスト用のユーザーでのみ機能を有効化、一般ユーザーでは無効のままにする。といった運用ができます。

user = User.find(...)
other_user = User.find(...)

Flipper.enabled?(:verbose_logging) # false
Flipper.enabled?(:verbose_logging, user) # false

Flipper.enable_actor(:verbose_logging, user)

Flipper.enabled?(:verbose_logging) # false
Flipper.enabled?(:verbose_logging, user) # true
Flipper.enabled?(:verbose_logging, other_user) # false

ただし、FlipperでActor機能を使うには、対象が一意の flipper_id を持つ必要があります。 それ用のmoduleを利用するか、自前でメソッドを定義しておきましょう。

class User < Struct.new(:id)
  include Flipper::Identifier
end

User.new(5).flipper_id # => "User;5"

class User < Struct.new(:uuid)
  def flipper_id
    uuid
  end
end

User.new("DEB3D850-39FB-444B-A1E9-404A990FDBE0").flipper_id
# => "DEB3D850-39FB-444B-A1E9-404A990FDBE0"

どのレイヤーでFeature Toggleを利用すべきか

Controller

Controllerは最も分岐させやすいでしょう。 before_actionを活用して新機能を封じる分岐を設置しておけば、本番環境では一切動作させずに開発環境で実装を進めることができます。

class SugoiNewFeatureController < ApplicationController
  def index
    if Flipper[:sugoi].enabled? current_user
      # 新機能向けの処理
    else
      # 新機能を表示させたくないときの処理
      render_404
    end
  end
end

筆者はこういったラッパーを用意してController/Viewから簡単に分岐できるようにしています。

module DarkLaunch
  extend ActiveSupport::Concern

  included do
    helper_method :authorized_feature?
  end

  def authorized_feature?(sym)
    Flipper[sym].enabled? current_user
  end

  def authorized_feature!(sym)
    raise UnavailableFeatureError unless authorized_feature?(sym)
    # 注: ↑任意の例外クラスとそれを拾って404ページを表示する機構を別途設ける必要あり
  end
end

使い方はこんな感じです。

class SugoiNewFeatureController < ApplicationController
  before_action -> { authorized_feature!(:sugoi) }

  def index; end
end

View

View側で新機能への画面遷移などを設置する場合は、ここにも分岐が欲しくなります。

%ul
  %li
    = link_to 'メニュー項目A', ****_path
  %li
    = link_to 'メニュー項目B', ****_path
  - if Flipper[:sugoi].enabled? current_user
    %li 
      = link_to 'すごい新機能!', ****_path

実装は容易ですが、遷移元それぞれに分岐を設置する必要があるため、塞ぎ忘れが発生しやすくなります。 導入するかどうかは慎重に検討しましょう。

Model

Model内でもFlipperを使うことはできますが、他の箇所よりはオススメできません。 利用ユーザーが参照しづらいためFlipperの機能が活かしづらくなります。

また、モデル内の処理は参照タイミングが多くなりがちで、テストすべき分岐が膨大になる可能性もあります。 コードの見通しも悪くなりがちなので、慎重に利用するのがよいでしょう。

よくあるトラブル

制御漏れによる不具合

Feature ToggleはON/OFFの制御を提供するのみの機構なので、開発者の塞ぎ忘れには無力です。 しっかりとテストしましょう。 「開発中のコードがデプロイされる」というリスクは確実に発生します。リリース戦略はしっかり立てましょう。

Feature Toggleで防げないレイヤーの不具合

機能自体をOFFにしたつもりでも防げないトラブルもあります。 例えばDBレイヤーの問題。 新機能向けのALTER TABLEを発行したところ、重要なテーブルがロックされてしまい、サービス障害に繋がることもあるかもしれません。 Toggleを過信せずに開発を行いましょう。

大量のif分岐が生まれる

Feature Toggleが必要になる大きな新機能は、リリース後も継続的に改善していくケースがあります。 新機能リリースのたびに分岐を追加していると、あっという間に分岐まみれになってしまいます。

- if flag1
  .hoge-wrapper
    - if flag2
      .feature-2
    - if flag3
      = render 'hogehoge'
      - if flag4
        = link_to fugafuga

使い終わったフラグはなるべく削除するようにしましょう。

新任メンバーがフラグの存在に気が付かない

新任メンバーがフラグの存在を知らず、機能そのものに気づかないケースもあります。 開発用のseedデータではフラグが全てONになるようにしておくと自動で機能が使えるようになるので便利です。

まとめ

以上、RailsにおけるFeature Toggleの導入方法の1つを紹介させていただきました。 比較的簡単に導入できますが、安易に利用するとトラブルに見舞われる場合があります。

「ユーザーに見えないAPIからリリースする」「モデルやテーブルだけ先に作っておく」など、まずはFeature Toggleを使わない選択肢を検討してから、必要な部分にのみToggleを導入していくのが賢い使い方でしょう。

この記事が、開発者の皆様の安定したリリースの助けとなれば幸いです。

また、今回は紹介できませんでしたが、Feature Toggleのためのgemは他にもRolloutなどが存在します。 皆様のお好みの方法で導入してみてください。


是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html