railsで作ったapiの利用可否を切り替えられるように、feature flag(機能フラグ)を導入したいということで、flipperというgemを使いました。
環境は、rails6.0.3 apiモードです。
feature flag(機能フラグ)とは
アプリケーションの各種機能の有効無効を、コードはそのままに、再デプロイもせずに、実行中のプログラムの外部から切り替えるための仕組みです。
少し細かく書くと、
- ある機能の使用可否を判定するフラグを用意でき、
- フラグのON/OFFは、ユーザ全体だけでなく、特定のユーザや、ユーザのグループ単位でも行える
- フラグのON/OFFは、コードの変更なしに、railsコンソール上や、外部からweb UIやapiで行える
といったことを実現するものです。
gemの導入手順
- まず、Gemfileに下記を設定します。
gem 'flipper' # flipper本体
gem 'flipper-active_record' # フラグを永続化するため
※フラグの永続化については「アダプタについて」のところで書きます
- インストールします。
$ bundle install
$ rails g flipper:active_record
$ rails db:migrate
- 次に、
config/initializers
ディレクトリにflipper.rb
を追加
require 'flipper'
require 'flipper/adapters/active_record'
Flipper.configure do |config|
config.default do
adapter = Flipper::Adapters::ActiveRecord.new # アダプタをActiveRecordアダプタとする
Flipper.new(adapter)
end
end
feature flagの使い方
たとえば、フラグ :hoge
があって、このtrue/falseに応じて挙動を切り替えたい箇所がある場合、そこにFlipper.enabled?(:hoge)
を埋め込みます。
(ユーザー単位で有効/無効が設定されているフラグの場合は、Flipper.enabled?(:hoge, user)
)
例1
ログイン中ユーザに対して、フラグ :hoge
が有効な場合だけ、特定のコードを実行する
if Flipper.enabled?(:hoge, current_user)
# 実行したいコード
end
例2
ログイン中ユーザに対して、フラグ:hoge
が有効な場合だけ、特定のactionを実行可能とする
class FugaController < ApplicationController
before_action -> {
require_feature(:hoge, current_user)
}, only: :show
def show
# 省略
head :ok
end
end
class ApplicationController < ActionController::API
private
def require_feature(flag_name, user)
return if Flipper.enabled?(flag_name, user)
raise Forbidden, '実行権限がありません'
end
end
フラグの有効化
フラグ:hoge
を有効化するには下記のようにします。
※rails consoleで直接叩いても、rake taskでも、controllerでも何処に書いてもいいです。
全てのユーザで有効化する
user = User.find(...)
# 有効化前は無効
Flipper.enabled?(:hoge)
=> false
Flipper.enabled?(:hoge, user)
=> false
# 有効化
Flipper.enable(:hoge)
# (ユーザに関係なく)有効になる
Flipper.enabled?(:hoge)
=> true
Flipper.enabled?(:hoge, user)
=> true
ユーザ単位で有効化する
特定のユーザのみでフラグを有効化したい場合
user = User.find(...)
other_user = User.find(...)
# 有効化前は無効
Flipper.enabled?(:hoge)
=> false
Flipper.enabled?(:hoge, user)
=> false
# userに対して有効化
Flipper.enable_actor(:hoge, user)
# 有効化したuserのみ有効になっている
Flipper.enabled?(:hoge)
=> false
Flipper.enabled?(:hoge, user)
=> true
Flipper.enabled?(:hoge, other_user)
=> false
group単位で有効化する
特定の条件に該当するユーザたち(group)のフラグを一括で有効かしたい場合
まず、groupを予め定義しておく必要があります。config/initializers/flipper.rb
に以下のような定義を書きました。(他に良い場所が思いつかなかったのですが、もし別案あったら追記します)
# groupを定義します
Flipper.register(:admins) do |actor, context|
actor.respond_to?(:admin?) && actor.admin?
end
次に、groupに対してフラグを有効化
user = User.find(...)
admin = User.find(...)
# 有効化前は無効
Flipper.enabled?(:hoge, user)
=> false
Flipper.enabled?(:hoge, admin)
=> false
# groupに対してフラグを有効化
Flipper.enable_group(:hoge, :admins)
# groupに属するuserのみ有効になる
Flipper.enabled?(:hoge, user)
=> false
Flipper.enabled?(:hoge, admin)
=> true
console上ではなく、initializersで、フラグを有効化してしまいたい場合の書き方
config/initializers/flipper.rb
でフラグの有効化まで行いたい場合は次のように書きます。
require 'flipper'
require 'flipper/adapters/active_record'
Flipper.configure do |config|
config.default do
adapter = Flipper::Adapters::ActiveRecord.new # アダプタをActiveRecordアダプタとする
Flipper.new(adapter)
end
end
# groupを定義します
Flipper.register(:admins) do |actor, context|
actor.respond_to?(:admin?) && actor.admin?
end
# フラグの有効化
if ActiveRecord::Base.connection.data_source_exists? 'flipper_features'
Flipper.enable_group(:hoge, :admins)
end
最後3行がフラグの有効化をしているところです。
ifブロックはハックなのですが、initializerでフラグを有効化しようとすると、アダプタ用のテーブルがセットアップされていない場合、「そんなテーブルはない」と言われてしまうため、このようなifブロックで囲んでおく必要があります。
アダプタについて
initializerでは、flipperで設定したフラグの値を保持するために使うアダプタを設定します。
アダプタには、Redis
や、Active Record
などが指定できます。
flipper本体には、メモリアダプタというアダプタが内蔵されているので、
gem flipper-active_record
などを入れなくても、下記のような設定が可能です。
require 'flipper'
Flipper.configure do |config|
config.default do
adapter = Flipper::Adapters::Memory.new # アダプタをメモリアダプタとする
Flipper.new(adapter)
end
end
ただし、これだとフラグが永続化されません
つまり、consoleなどでフラグを有効化をしても、有効なのはコンソール上だけで、apiなどからフラグを参照した場合は、無効のままになってしまいます。
公式でも、アダプタには「アクティブレコードアダプタのような永続的な物を強く推奨します」と書かれていました。
どんなアダプタがあるか
こちらのページにある通り、以下のようなadapterがサポートされているようです
- ActiveRecord adapter
- ActiveSupportCacheStore adapter
- Cassanity adapter
- Http adapter
- memory adapter
- Moneta adapter
- Mongo adapter
- PStore adapter
- read-only adapter
- Redis adapter
- Sequel adapter
- Community Sup
公式ドキュメントには、あるアダプタを使っていて途中から、別のアダプタに乗り換えたい時の手順についても書かれています。
さいごに
flipperをかんたんに紹介しました。
他に、ユーザのうち指定のパーセントの任意のユーザのフラグを有効化するという機能(A/Bテストやカナリアリリースを想定したような機能)もあるようです。
また、フラグの確認や設定のためのwebインターフェースや、apiも用意されています。
まだ、そういった機能は使っていませんが、必要に応じて、他の機能も調べたり試したりしてみようと思います。