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

ClinPeer Railsプロジェクトの設定値管理(2025年版)

$
0
0

こんにちは。サーバーサイドエンジニアの三村(@t_mimura39)です。

こちらでご案内した通り、弊社で新しくリリースした「ClinPeer」の裏側をご紹介します。 tech.medpeer.co.jp

今回は小ネタとして「Railsプロジェクトの設定値管理」についてご紹介します。

目次

設定値管理とは

ここでは「実行環境毎に異なる値」を「Rubyなどのプログラムコードとは別のYAMLなどのファイル」で管理する方法について言及します。
例えば、「SaaSのAPIを利用するための接続情報」だったり、もっと単純に「異常発生時の通知先Slackチャンネル」のようなものがあります。
この仕組みを利用することでプログラムの複雑さを軽減することができ、可読性の向上に繋がります。

before

# 環境差分を設定値として切り出さずにプログラム内でif分岐defhostifRails.env.production?
    "prd.example.com"else"dev.example.com"endend

after

# 環境差分を設定ファイルに切り出すことでプログラムが単純化するdefhostSetting.host
end
production:host: prd.example.com
development:host: dev.example.com

Railsプロジェクトでの設定値管理の選択肢

Settingslogic

github.com

馴染み深い方も多いのではないでしょうか。
シンプルな機能で使い勝手も良かったため私も多用していました。

しかし、ここ最近 メンテナンスが停滞しており今後の継続利用が難しい状況です。
脱Settingslogicを検討した・されているプロジェクトが多くあるかと思います。

SettingsCabinet

github.com

Settingslogicの置き換えを狙って作成されたGemです。
採用事例は少ないですが、薄い実装のGemのためSettingslogicの使い勝手が特に気に入っている方はこちらを検討してみると良いかと思います。 zenn.dev

その他諸々Gem

他にも様々な設定値管理用のGemがありますがここでは割愛します。
以下のようなGemがありますが、いずれも機能過多に感じます。

github.comgithub.comgithub.comgithub.com

Rails標準 config_for

というわけで、今回採用した方法はこちらになります。

api.rubyonrails.org

脱Settingslogicからconfig_forに乗り換えたプロジェクトも多いのではないでしょうか。
しかし、config_forには「設定値をネストできない」という弱点があります。正確にはネストはできますがHashでの管理となるため参照する際に気持ちが良くありません。

# config/dummy.ymldevelopment:a:b: c
Rails.application.config_for(:dummy)
#=> {a: {b: "c"}}Rails.application.config_for(:dummy).a
#=> {b: "c"}Rails.application.config_for(:dummy).a[:b]
#=> "c"Rails.application.config_for(:dummy).a.b
undefined method 'b'for an instance of Hash (NoMethodError)

ClinPeerでは少し工夫してconfig_forを利用することで本弱点を回避しています。

config_forの活用方法(定義・参照)

一つの config/application.ymlなどに全ての設定値を集約定義するのではなく、設定値の種類毎にファイルを分離して管理しています。
以下のような使い方です。

$ tree config/settings/
config/settings/
├── application.yml
├── firebase.yml
├── host.yml
├── rollbar.yml
├── sendgrid.yml
├── slack.yml
└── zendesk.yml
# config/settings/sendgrid.ymldevelopment:api_key: dummy_development_api_key
test:api_key: dummy_test_api_key
Rails.env
#=> "development"Setting::SendGrid.api_key
#=> "dummy_development_api_key"

このように管理する理由は二つあります。
まず一つ目は「単純に管理がしやすい」点です。全ての設定値を一つの設定ファイルで管理すると数百、数千行にもなることがあり管理が煩雑になります。

二つ目は「config_forの弱点(設定値のネストがしにくい)を軽減できる」点です。設定値のネストがしたくなる粒度でファイルを分割することで本問題を回避することができます。ファイルが量産されることによる管理の煩雑さが懸念としてありますが、現状問題になったことはありません。

config_for活用方法(裏側)

このような形で Setting名前空間配下に設定値種類毎の専用モジュールを定義する形で実装しています。

# config/initializers/002_setting.rbmoduleSetting
  {
    "Application" => "config/settings/application.yml",
    "Firebase" => "config/settings/firebase.yml",
    "Host" => "config/settings/host.yml",
    "Rollbar" => "config/settings/rollbar.yml",
    "SendGrid" => "config/settings/sendgrid.yml",
    "Slack" => "config/settings/slack.yml",
    "Zendesk" => "config/settings/zendesk.yml",
  }.each do |k, v|
    const_set k, Rails.application.config_for(Rails.root.join(v))
  enddefself.method_missing(name)
    Application[name]
  enddefself.respond_to_missing?(name, _include_private)
    Application.key?(name)
  endend

ちなみに、 application.ymlにはより汎用的な設定値を定義しており Setting.revisionのようなシンプルなI/Fで参照できるようにしています。

なぜ config.x.を使わないのか

Rails Guideでは config.x.payment_processing.schedule = :dailyconfig.payment = config_for(:payment)のような使い方が紹介されています。
もちろんこのような形でも利用可能ですが、 config.x.payment_processing.scheduleのような参照方法は長く気持ちが良くないため採用しませんでした。
Setting::PaymentProcessing.scheduleのようなI/Fの方が美しいですよね。

また、I/Fが美しいということはテスト時にもモックしやすいと同義です(要出典)。

allow(Setting::PaymentProcessing).to receive(:schedule).and_return(:daily)

なぜ動的に定義しないのか

Rails.root.glob("config/settings/*.yml")のような形で config/settings配下に配置されているファイルに対応するモジュールを動的に定義することも可能です。が、ClinPeerはあえてそのような形にはせずに先のような「モジュール名」と「ファイルパス」のマッピングを定義しています。
大した理由はないのですが、余談程度にご説明します。
設定値の多くはSaaS毎に定義されます。SaaSの名称は専用のinflection定義が必要になる場面があり(SendGridなど)その管理コストを払うくらいであれば明示的にマッピング定義をした方が分かりやすくて良いだろうと判断した結果になります。
あとはシンプルにコード検索もしやすいですね。

こういった仕組みは一度作られると中々改善されないので、些細なところまでこだわりました。

秘匿値管理方法

少し話を変えて、ClinPeer内での秘匿値の管理方法についてご紹介します。
秘匿値の特性に応じて主に2種類の管理方法を使い分けています。

管理方法 参照・更新可能者 特性
環境変数 DBパスワード、JWT秘密鍵 一部のリードレベルのエンジニアのみ 漏洩すると個人情報の流出につながるリスクのある秘匿性の高い値
Rails標準のCredential管理 SaaSのAPIキー ClinPeer開発エンジニア全員 万が一漏洩してもイタズラ程度でしか悪用されることがない値

環境変数

実際の秘匿値はRailsリポジトリとは別のTerraform管理用リポジトリで暗号化された状態で管理されています。
Rails ECSコンテナ起動時に環境変数として注入されるため、一部のエンジニア以外は設定値に触れることなくセキュアに管理することができます。

一方で、秘匿値を追加する際に二つのリポジトリを跨いで修正し、デプロイに関しても順序を気をつける必要があるため若干管理が煩雑になるデメリットがあります。

Rails標準のCredential管理

皆様お馴染みの bin/rails credentials:editそれです。

ClinPeerではRails標準の「development, test, production」の他に「staging」というRAILS_ENVも定義しています。
Multi Environment Credentialsの機能を活用して環境毎に分離して秘匿値を管理しています。

「Multi Environment Credentials」って便利なのにRails公式のドキュメントに記載ないのですよね。以下のブログが詳しそうだったため詳細はこちらをご参照ください。 blog.saeloun.com

ClinPeerプロジェクト発足時にはこれらのどちらかを選択するか悩みましたが、どちらを利用しても良いのだということに気がつき適宜使い分ける運用としています。

参照方法

秘匿値の管理方法は上記の通り2種類ありますが、アプリケーションコード上で参照する際にはその点を意識しなくて済むような形としたいです。
設定YAMLファイルの中でERB評価する形で値を埋め込むことで、Setting::SendGrid.api_keySetting::Rollbar.server_access_tokenのような統一的なI/Fで参照できるようになっています。

# config/settings/sendgrid.ymldevelopment:api_key: dummy_api_key
test:api_key: dummy_api_key
staging:api_key:<%= ENV["SENDGRID_API_KEY"] %>
production:api_key:<%= ENV["SENDGRID_API_KEY"] %>
# config/settings/rollbar.ymldevelopment:server_access_token:test:server_access_token:staging:server_access_token:<%= Rails.application.credentials.dig(:rollbar, :server_access_token) %>
production:server_access_token:<%= Rails.application.credentials.dig(:rollbar, :server_access_token) %>

Rubyな値を一度YAMLに変換した後にconfig_forでRubyな値に変換するという若干無駄なコストを払っていますが、この使い方が今のところ一番扱いやすく感じています。

おわり

前回のブログ記事(ClinPeer Railsプロジェクトの技術選定(2025年版) - メドピア開発者ブログ)とは違い、少し実装に踏み込んだ話をしてみました。
今回のような小ネタが続く予定ですが、どうぞお楽しみください。

(オマケ)Slack通知先チャンネルの管理方法について

ClinPeerでは特定のイベント発生をトリガーとして、Slackチャンネルに通知するような実装がいくつかあります。
実行環境毎に通知先を分ける必要があり、その通知先管理についても本config_forの仕組みを活用しています。

その際のTipsとして、「通知先チャンネル名」をそのまま設定値として管理するのではなく「通知先チャンネルのID」を管理することをオススメします。 (SlackのAPIは通知先を指定する際にチャンネル名でなくチャンネルIDを受け付けているため、大抵のコードは置き換えるだけで動作するはずです)

# config/settings/slack_channel.ymldevelopment:ops:"C07AAAAAAAA" # playgroundtest:ops:"C07AAAAAAAA" # playgroundstaging:ops:"C07AAAAAAAA" # playgroundproduction:ops:"C07BBBBBBBB" # ops

このようにしておくと、万が一Slackのチャンネル名が変更された際にも通知処理でエラーにならずに継続動作します。 設定値管理とは本質的には無関係の超小ネタでした。


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


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

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

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

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

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


Viewing all articles
Browse latest Browse all 215

Latest Images

Trending Articles



Latest Images