Ralisへ機能フラグ(Feature Flag)を追加し各フラグを制御する

この投稿は、弊社が提供するWESEEK TECH通信の一環です。
WESEEK TECH通信とは、WESEEKのエンジニアがキャッチアップした技術に関する情報を、techブログを通じて定期的に発信していくものです。

はじめに

初めまして、システムエンジニアの蛸井です。
今回の記事では Feature Flag とは何なのか、それを利用するメリット、使用方法について解説します。
そしてこの Feature Flag を扱うために、FlagShifTzu という gem を用いて実際に動かしてみます。

Feature Flag とは

Feature Flag とは、1つのカラムで複数のフラグを制御する機能です。
通常、フラグとして扱うカラムには boolean 型を設定し、中身に true, false を保存することによってフラグの on, off を切り替えます。
なので、複数の boolean 値、つまり複数のフラグを保存する場合はその数分のカラムが必要になります。
しかしこの Feature Flag を用いることによって、1つのカラムに擬似的に複数のフラグを保存できるようになります。

Feature Flag の原理

どういう原理で複数の boolean 値を管理するのかを解説します。
複数のフラグを管理するカラムを integer 型で作成します。
このカラムの数値によってどの項目が true, false であるかが決まります。

今回は User モデルを用意し、そこに flags カラムを用意します。
そして flags カラムには、例としてIT系の各資格の保有有無を保存することにします。

それぞれの資格を以下のように数字で定義します。

  • 1 ・・・ network_specialist(ネットワークスペシャリスト)
  • 2 ・・・ database_specialist(データベーススペシャリスト)
  • 4 ・・・ system_architect(スシテムアーキテクト)
  • 8 ・・・ project_manager(プロジェクトマネージャー)
  • 16 ・・・ it_strategist(ITストラテジスト)
  • 32 ・・・ it_service_manager(ITサービスマネージャー)

そして、この定義した数字を足し合わせていくことでユーザーがどの資格を持っているのかを表します。

  • 例1 ユーザーAが資格を1つも持っていない場合、flags の値は 0 になる
  • 例2 ユーザーBがネットワークスペシャリストとデータベーススペシャリストを持っている場合、 flags の値は 1 + 2 で 3 になる
  • 例3 ユーザーCが全ての資格を持っている場合、flags の値は 1 + 2 + 4 + 8 + 16 + 32 で 63 になる

これにより、決められた数字を保存することで、1つのカラムに擬似的に複数の boolean 値を保存することができます。

Feature Flag を用いるメリット

1つ目のメリットは、本来複数必要だったカラムを1つのカラムで管理できるようになるため、ファイルがスッキリします。

2つ目は、扱う項目の変更、増減が簡単にできます。
Rails ではカラムを追加するたびにマイグレーションファイルを作成してデータベースに適用させるという作業を行います。
Feature Flag を用いる場合は、モデルファイルに記述する設定を書き換えるだけで変更、増減ができます。
boolean 型のカラムが頻繁に追加されるような場合であれば、このように Feature Flag を用いた方が楽に開発を行えます。

複数のフラグを1つのカラムに保存する方法は、Feature Flag を用いる他に、配列やハッシュを用いるという方法があります。
しかし後述の gem を用いることで、配列やハッシュを用いるよりも楽にフラグを管理することができます。

FlagShifTzu を実際に動かしてみる

Feature Flag を実装できる gem は、例えば以下のようなものがあります。

上2つの ActiveFlag, Flipper に関しては日本語で解説されている記事があったので、気になる方はそちらをご覧ください。

今回は FlagShifTzu を用いて Feature Flag を動かしてみます。
先ほどの「Feature Flag の原理」で解説したように User モデルに flags カラムを追加して、資格の保有有無を管理できることを目指します。

まず初めに gem のインストールを行います。
Gemfile に flag_shif_tzu を書き込んで bundle install を実行します。

gem 'flag_shih_tzu'
$ bundle install

そして User モデルに flags カラムを追加するためにマイグレーションファイルを用意します。

$ bin/rails generate migration AddFlagsToUser

マイグレーションファイルに以下の記述を追加します。
このとき、null false にしてデフォルト値は 0 にします。

class AddFlagsToUser < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :flags, :integer, default: 0, null: false
  end
end

データベースに反映させます。

$ bin/rails db:migrate

これで User モデルに flags カラムが追加されました。
次に flag_shif_tzu の設定を行います。user.rb に以下の記述を追加します。

class User < ApplicationRecord
  include FlagShihTzu

  has_flags(
    1 => :network_specialist,
    2 => :database_specialist,
    3 => :system_architect,
    4 => :project_manager,
    5 => :it_strategist,
    6 => :it_service_manager
  )
end

これにて設定は完了です。これにより network_specialist, database_specialist などのメソッドが自動的に追加され扱えるようになります。
追加されたメソッドをカラムのように扱うことでフラグを変更することができます。

ちなみに has_flags 内に :column => 'カラム名' という記述を追加することで、flags 以外のカラム名を使用することも可能です。

今回はコンソール上で User モデルをいじるので rails console でコンソールを起動します。

$ bin/rails rails c

試しに network_specialist のみが true の User を作成します。

$ User.create(name: 'admin', email: 'admin@example.com', network_specialist: true)
=> #<User:0x00007f0330d00000 id: 1, name: "admin", email: "admin@example.com", flags: 1>
$ User.find(1).flags
=> 1
$ User.find(1).network_specialist
=> true
$ User.find(1).database_specialist
=> false

network_specialist のみ true になっており、flags の値が内容によって自動で変更されることが確認できました。

update も当然出来ます。

$ User.find(1).update(project_manager: true, it_service_manager: true)
$ User.find(1).flags
=> 41

flags の値は 1 + 8 + 32 で自動で 41 になっています。

さらに、all_flags メソッドを使うことで制御可能な全てのフラグを確認することができ、
selected_flags メソッドで true になっているフラグを取得することができます。
(注意 : FlagShifTzu のバージョンによってはメソッド名が all_features, selected_features になっているかもしれません)

$ User.find(1).all_flags
=> [:network_specialist, :database_specialist, :system_architect, :project_manager, :it_strategist, :it_service_manager]
$ User.find(1).selected_flags
=> [:network_specialist, :project_manager, :it_service_manager]

id 1 のユーザーがネットワークスペシャリスト、プロジェクトマネージャー、ITサービスマネージャーの 3 つの資格を持っていることが分かりました。

また、モデルに対してフラグ名を実行させることで、フラグが true になっているすべてのレコードを取得できます。

$ User.network_specialist
=> [
  #<User id: 1, user_name: "admin", email: "admin@example.com", flags: 41>,
  #<User id: 2, user_name: "test", email: "test@example.com", flags: 1>
]
$ User.network_specialist.count
=> 2

もしこの flags カラムを配列やハッシュで管理していた場合は、今紹介したいくつかの操作が少しが面倒になりますが、gem を用いて Feature Flag を使うことで随分と楽に管理することができました。

おまけ

余談ですが、もちろんそれぞれのフラグを変更するのではなく flags の数値を直接変更することで複数のフラグを一気に変更することもできます。しかし自分で数値を計算してあげる必要があります。

ちなみに FlagShifTzu では condition メソッドが追加され、以下の例だと network_specialist が true になるような flags の数字を全て取得することができます。

$ User.network_specialist_condition
=> "(\"users\".\"flags\" in (1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63))"

ただ gem のおかげで、まるでカラムが追加されているかのように操作を行えるため、普通に使う分には flags の値というのは気にする必要がありません。

最後に

FlagShifTzu gem を用いて Feature Flag を実際に動かしてみたところ、1つの flags というカラムだけでいくつものフラグを管理することができました。
多くのフラグを扱う時に非常に便利なので、機会があれば是非使用してみてください。