Rails: アプリで最も重要な"境界"とは(翻訳)
このところ、もっぱら前回の私の記事でも触れた作業が続いています。Rails 4.2からRails 5.0へのアップデート、そしてRails 5.1へのアップデートの計画を立案していて、アプリケーションをフレームワークの内部詳細に癒着させないことがどれほど重要かが痛いほど身に沁みました。
癒着を切り離すことの重要性を示すために、Rails 4.2からRaisl 5.1にかけてActionController::Parametersで行われた変更を見てみましょう。
🔗 Rails 4.2
Rails 4.2のActionController::Parametersは、RubyのコアクラスであるHashクラスのサブクラスだったので、他のハッシュと同じように使え、受け渡しも楽でした。
🔗 Rails 5.0
Rails 5.0で大きな変更が発生しました。 ActionController::ParametersがHashのサブクラスではなくなり、完全に別クラスとなったのです。この変更はセキュリティの強化とマスアサインメント脆弱性防止のために実装されました。しかしmissing_methodフックを使えばハッシュのすべてのメソッドを引き続きActionController::Parametersで利用できました。
🔗 Rails 5.1
missing_methodフックがRails 5.1で削除されてしまいました。しかもActionController::Parametersと通常のRubyのハッシュが切り離されて、#eachや#mapや#each_keyや#each_valueといったメソッドはActionController::Parametersでもう使えなくなりました1。
Railsバージョンごとのpublic APIは、以下のように大変な変わりようです。

私たちが取り組んでいるアプリでは、コントローラからService層が呼び出されています。このService層は一部のビジネスロジックを担当しており、結果の多くはデータベースで永続化されます。
運の悪いことに、このService ObjectがActionController::Parametersで初期化されていたのです。
そのあちこちで、Hashを継承しているかどうかのチェックが行われていました。
# ...
return results unless hash.kind_of?(Hash)
# ...
これらはさらにストレージ層にまで渡され、Active Recordのserializeメソッドでシリアライズされていました。
class Attachment < ActiveRecord::Base
serialize :data, Hash
# ...
end
Railsのアップグレードに取りかかろうとすると、既存のテストが落ちるようになり、ActiveRecord::SerializationTypeMismatchエラーが大量に発生しました。
一見シンプルなRailsのアップグレードだったのに、ActionControllerモジュールの内部と癒着していたせいで、途方もない時間がかかることが判明しました。こうした問題を解決するには、ドメインロジックとフレームワーク内部との境界を越境しないようにしておくことが重要です。
フレームワークのオブジェクトをドメインロジックに渡すときはくれぐれもご注意ください。Ruby標準の型か、独自のValue Objectを使うようにしましょう。
境界で仕切ることで、多くのメリットを得られます。
- 柔軟性が高まる
- 境界で区切ることで、アプリ全体を書き直さずにフレームワークやライブラリ、場合によっては言語すら切り替え可能になります。
- テストしやすくなる
- ドメインロジックをフレームワーク内部から切り離すことで、複雑なセットアップや依存関係なしにコア機能を楽にテストできるようになります。
- メンテナンスしやすくなる
- 癒着していないアプリケーションは、関心(concern)が明確に分離されて問題の特定や修正も容易になるので、メンテナンスしやすくなります。
今後開催予定のRailsビジネスロジックマスターコースでも同様の問題を取り上げる予定です。お見逃しなきよう、元記事末尾のフォームでぜひニュースレターを購読してください。
概要
元サイトの許諾を得て翻訳・公開いたします。