はじめに
皆様こんにちは、サーバーサイドエンジニアの草分です。
突然ですが、開発者の皆様、実装したソースコードはこまめにリリースしていますか? 「大きい機能なので開発に時間がかかる」「障害が起きないよう念入りにテストする必要がある」などの理由で、Featureブランチのままコミットグラフが伸びに伸びたりしていませんか?
大きな機能を作ること自体は悪いことではありませんが、大きすぎるFeatureブランチは、本流ブランチとの挙動の乖離やコードの衝突が発生しやすく、レビューやマージに多大な苦労を伴います。
この記事では、この問題の解決策の1つとなる「Feature Toggle」を、Ruby on Railsにおける実装方法と共にご紹介します。 Feature Toggle自体は開発手法の一種であるため、言語/フレームワークを問わず広く活用されています。
Feature Toggleとは
- 「機能が動作する/動作しない を切り替える」機構です。
- ソースコードは存在しますが、トグルを「ON」にするまで機能が動作しないよう制御します。
効果
Featureブランチの早期マージが可能となる
通常、作りかけの機能を本流ブランチにマージしてしまうと、ユーザーに未完成の機能を提供することになるため、問題になってしまいます。
そこでFeature Toggleを用いて、開発中の機能はトグル「OFF」の場合動作しないように実装します。そうすることで、開発中の機能をユーザー環境に影響を出さずにマージできるようになります。
本番環境でのテストが可能となる
本番環境では、開発環境では見つからなかった問題が多々発生します。 例えば、既存データに予期せぬレコードが入っていたり、アプリではない別のレイヤーでトラブルが起きたり...
Feature Toggleで社内向けのテストユーザーのみ機能を利用できるよう制御すれば、実際のユーザー環境にリリースする前に、テストユーザーで機能の動作確認ができるようになります。
Railsでの実装例
この記事では、Feature Toggle実装の助けとなってくれる「Flipper」 gemをご紹介します。 このgemはフラグ管理と分岐制御の仕組みを提供しています。
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#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/
■開発環境はこちら