Quantcast
Channel: メドピア開発者ブログ
Viewing all articles
Browse latest Browse all 215

環境ごとの設定管理が可能な ClinPeer のフィーチャーフラグの紹介

$
0
0

こんにちは。サーバーサイドエンジニアの佐藤太一(@teach_kaiju)です。

今回の「ClinPeerアプリ開発の裏側連載記事」ではサーバーサイドにおける、フィーチャーフラグの実装方法を紹介します。

tech.medpeer.co.jp

目次

フィーチャーフラグとは

フィーチャーフラグ(機能フラグ、別名フィーチャートグル)は、機能のオン・オフを制御する仕組みです。
ClinPeer ではフィーチャーフラグを用いることで、開発中の機能の細かいリリースや、社内IPのみ機能を有効にするなどの柔軟な制御を実現しています。

フィーチャーフラグに関する詳細は以下の記事に書かれています。こちらもぜひご覧ください。

tech.medpeer.co.jp

機能の有効化 Feature#enabled?

ClinPeer では Flipper gem をラップした Featureクラスを用いています。

基本的な使い方

特定の機能(例:allow_access_to_new_sugoi_feature)が有効かどうかを調べるには、以下のように記述します。

ifFeature::ALLOW_ACCESS_TO_NEW_SUGOI_FEATURE.enabled?

特定の条件で有効化

enabled?メソッドには、オプションで引数を渡すことができます。この引数を使うことで、「特定のユーザーだけに機能を有効にする」「特定のIPアドレスからアクセスされた場合のみ機能を有効にする」といった、より細かい制御が可能になります。

例えば、特定のIPアドレスからのアクセスに対してのみ機能を有効にしたい場合は、そのIPアドレス文字列を渡します。

ifFeature::ALLOW_ACCESS_TO_NEW_SUGOI_FEATURE.enabled?(request.remote_ip)

許可するIPアドレスは、Flipper UI で設定します。

flipper_ui_allow_ip

フラグの運用

フィーチャーフラグの定義や操作は、主に config/features.ymlファイルと Flipper UI を通じて行います。

features.yml

フィーチャーフラグの設定は、config/features.ymlファイルで一元管理されます。このファイルには、各フラグの識別子 (kind)、説明 (description)、そして環境ごとの設定を記述します。

# config/features.yml の例- kind: allow_access_to_new_sugoi_feature
  description:新しいスゴイ機能へのアクセスを許可する
  development: flipper
  test:truestaging: flipper
  production: flipper

各項目の意味は以下の通りです。

  • kind: フラグの一意な識別子です。コード中では Feature::KIND_NAMEのようにして参照できます。
  • description: フラグの説明です。この内容は Flipper UI のダッシュボードにも表示されます。
  • 環境名 (development, test, staging, productionなど):
    • flipper: Flipper UI でフラグの有効/無効を制御する場合に指定します。(デフォルト: 無効)
    • true: その環境では常にフラグを有効にします。
    • false: その環境では常にフラグを無効にします。

ClinPeer ではデプロイ時にseed を実行し、その中で features.ymlの内容をもとに差分を更新します。

# app/models/feature.rb の抜粋classFeature< ActiveYaml::BaseincludeActiveHash::Enum# 略
  
  set_root_path Rails.root.join("config")

  enum_accessor :kind

  scope :flipper_controllable, -> { where(Rails.env => FLIPPER_VALUE) }

  FLIPPER_VALUE = "flipper"private_constant:FLIPPER_VALUE# 略end
# seed の処理

features = Feature.flipper_controllable.pluck(:kind) # ymlからFlipper制御対象のkindを取得
current_features = Flipper.features.map(&:name) # 現在Flipperに登録されている機能名を取得# ymlにあってFlipperにないものを追加
(features - current_features).each { |f| Flipper.add(f) }
# Flipperにあってymlにないものを削除 (ymlから削除されたフラグ)
(current_features - features).each { |f| Flipper.remove(f) }

フラグの新規追加

config/features.ymlに新しいフラグの定義を追加します。 デプロイ時に seed で features.ymlの内容をもとに差分を更新します。

フラグの有効・無効の切り替え

config/features.ymlflipperと設定されているフラグの有効/無効は、Flipper UI (社内用管理画面) 上で操作します。

flipper_ui_on_off

フラグの削除

不要になったフィーチャーフラグを削除する際は、以下の手順で行います。

  1. フラグの参照箇所をコード上から削除
  2. 上記対応をリリース
  3. config/features.ymlから該当フラグの定義を削除

ポイント
「フラグ参照箇所の削除」と「ymlからのフラグ定義の削除」を同一のリリースに含めないようにしています。

  • Flipper は、存在しないフラグを参照した場合、無効 (false) として扱われます。
  • ymlの変更(フラグ定義の削除)を反映するデプロイタスクは、アプリケーションコードの反映よりも先に実行される場合があります。

もし同一リリースに含めてしまうと、ymlからフラグが削除された後、まだ古いコードがそのフラグを参照しているわずかな時間帯に、意図せず機能が無効化されてしまう可能性があります。

条件付き有効化の実装

Flipper には対象をflipper_idで識別し、一致した場合のみ機能を有効化するという機能があります。 具体的には以下の2つを比較し、一致した場合機能を有効化します。

箇所
enabled?の第二引数 puts some_obj.flipper_id # 127.0.0.1
Flipper.enabled?("allow_access_to_new_sugoi_feature", some_obj)
Flipper UI で設定した actor
flipper_ui_allow_ip

(actor は flipper_idという識別子を持ったオブジェクト。この識別子を比較することで actor が同一であるかどうかを判断しています。そして、actor が同一であれば機能を有効化します。)

https://www.flippercloud.io/docs/features/actors

ClinPeerでは StringFlipperActorクラスを導入することで、任意の文字列を直接Actorの識別子として扱えるように拡張しています。

# app/models/feature.rb の抜粋classFeature< ActiveYaml::Base# 略classStringFlipperActorattr_reader:valuedefinitialize(value)
      @value = value
    endaliasflipper_idvalueend# 略defenabled?(obj = nil)
    case value
    whenFLIPPER_VALUE
      obj = StringFlipperActor.new(obj) if obj.is_a?(String)
      Flipper.enabled?(kind, obj)
    else
      !!value
    endendprivatedefvalue
    public_send(Rails.env)
  endend

これにより、Feature#enabled?メソッドに文字列を渡すと、その文字列がそのまま flipper_idとして扱われます。

Feature::ALLOW_ACCESS_TO_NEW_SUGOI_FEATURE.enabled?("127.0.0.1") # "127.0.0.1" がflipper_idとなる

この仕組みを利用することで、IPアドレスや特定の識別文字列など、モデルオブジェクトが存在しないようなケースでも柔軟にActorベースのフラグ制御を行うことができます。

生成AIを活用したフラグ削除

フィーチャーフラグは、機能のリリースサイクルを柔軟にする強力なツールですが、役目を終えたフラグは適切に削除していく必要があります。フラグが増えすぎると、コードの複雑性が増し、管理コストも増大するためです。

従来、フラグの参照箇所の削除は以下の手順で行っていました。

  1. コードベース全体から、削除対象フラグの参照箇所を検索する。
  2. 特定された参照箇所を一つ一つ手動で修正・削除する。

このプロセスは、特に view 等の分岐が複雑な場合、時間と手間がかかり、見落としのリスクも伴いました。

そこで、現在はフラグの参照箇所の削除に生成AIを使用しています。プロンプトの例を以下に示します。

Feature::{フラグ名}は常に{true or false}なフラグとなりました。
上記を参照しているすべての条件分岐を削除してください。
features.ymlから対象のフラグの削除はしないでください。
features.ymlの該当のフラグに「TODO: 参照箇所削除済み、削除予定」というコメントを追加してください。
参考:
 - kind: allow_access_to_new_sugoi_feature # TODO: 参照箇所削除済み、削除予定
 
その後 features.ymlをコミットしてください
コミットメッセージ: Git履歴を残すために削除予定のコメント追加

AIを活用すると、手動と比較して、以下のようなメリットが見込めます。

  • 参照箇所の自動特定: AIがコードを解析し、削除対象のフィーチャーフラグが使用されている箇所を迅速に特定します。
  • 修正コードの提案:特定された箇所に対して、AIが適切な修正案(フラグ参照の削除や、条件分岐の恒久化など)を提案してくれる場合があります。
  • 作業時間の短縮とミスの削減:手作業による検索や修正と比較して、作業時間を大幅に短縮し、ヒューマンエラーによる見落としや修正ミスを減らすことができます。

最終的なコードの確認とテストは開発者自身が行う必要がありますが、AIツールを補助として利用することで、フィーチャーフラグのライフサイクル管理をよりスムーズかつ安全に行えるようになると考えています。

おわりに

本記事では、ClinPeerにおけるフィーチャーフラグの実装と運用方法について紹介しました。 Flipperという強力な基盤ライブラリを利用しつつ、FeatureというActiveHashモデルでラップすることにより、アプリケーション固有の事情や、より使いやすいインターフェースを開発チームに提供しています。

このように、フィーチャーフラグシステムを適切に抽象化(ラップ)することには、多くのメリットがあります。

  • 管理の容易化:フラグの定義を一元化 (features.yml) し、環境ごとの挙動を明確にすることで、管理コストを低減します。
  • 利用の簡便化:Feature::KIND_NAME.enabled?のような直感的で統一されたインターフェースを提供することで、開発者が迷うことなくフラグを利用できます。
  • 将来的な拡張性:例えば、将来的に別のフィーチャーフラグ管理システムに移行する場合でも、Featureクラス内部の実装を変更するだけで済み、アプリケーションコードへの影響を最小限に抑えることができます。
  • 独自のロジックの追加:StringFlipperActorのように、特定のニーズに合わせた独自のロジックを組み込みやすくなります。

フィーチャーフラグは、アジャイルな開発、安全な機能リリース、そしてA/Bテストなど、現代的なソフトウェア開発において非常に有効なプラクティスです。 ClinPeerでは、このような仕組みを活用し、ユーザーにより良い価値を迅速に届けられるよう、日々改善を続けています。

この記事が、フィーチャーフラグの導入や運用を検討されている方の一助となれば幸いです。


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


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

■募集ポジションはこちら medpeer.co.jp

■エンジニア紹介ページはこちら engineer.medpeer.co.jp

■メドピア公式YouTube  www.youtube.com

■メドピア公式note
style.medpeer.co.jp


Viewing all articles
Browse latest Browse all 215

Trending Articles