こんにちは。サーバーサイドエンジニアの三村(@t_mimura39)です。
こちらでご案内した通り、弊社で新しくリリースした「ClinPeer」の裏側をご紹介します。 tech.medpeer.co.jp
今回は小ネタとして「Railsプロジェクトの設定値管理」についてご紹介します。
目次
- 設定値管理とは
- Railsプロジェクトでの設定値管理の選択肢
- config_forの活用方法(定義・参照)
- config_for活用方法(裏側)
- 秘匿値管理方法
- おわり
- (オマケ)Slack通知先チャンネルの管理方法について
設定値管理とは
ここでは「実行環境毎に異なる値」を「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
馴染み深い方も多いのではないでしょうか。
シンプルな機能で使い勝手も良かったため私も多用していました。
しかし、ここ最近 メンテナンスが停滞しており今後の継続利用が難しい状況です。
脱Settingslogicを検討した・されているプロジェクトが多くあるかと思います。
SettingsCabinet
Settingslogicの置き換えを狙って作成されたGemです。
採用事例は少ないですが、薄い実装のGemのためSettingslogicの使い勝手が特に気に入っている方はこちらを検討してみると良いかと思います。
zenn.dev
その他諸々Gem
他にも様々な設定値管理用のGemがありますがここでは割愛します。
以下のようなGemがありますが、いずれも機能過多に感じます。
github.comgithub.comgithub.comgithub.com
Rails標準 config_for
というわけで、今回採用した方法はこちらになります。
脱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 = :daily
や config.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_key
Setting::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