Railsの利点と欠点、RailsがDDDや大規模開発に向いていない理由
知り合いが「Railsの長所ってなんやっけ?」って話をしてたので、その辺のRailsの長所・短所とか、RailsがDDDや大規模開発に向いてない理由を自分なりに整理してみた。
間違っている点などあれば是非ご指摘頂きたいです。
※2023/07/21 追記:
本記事においてユースケース
という言葉を多用していますが、文脈(コンテキスト)
という言葉を使用すべきだったかもしれません🙏
DDDにおいてモデルはユースケース
ごとに分かれるのではなく文脈
ごとに分かれるものであり、本記事でも文脈
というニュアンスでユースケース
という言葉を使用していましたが、適切な言葉を選ぶべきでした。
ex. 商品というモデルを販売コンテキスト、配送コンテキストに分ける(参考: 境界づけられたコンテキスト 概念編 - ドメイン駆動設計用語解説 [DDD] - little hands' lab)。
Rails(Active Record)の利点と欠点
RailsというかActive Recordの利点と欠点を話しているだけ。
- Railsの利点
つまりViewやControllerなどあらゆるコードがActiveRecordと密結合になるので、例えばViewに渡っているデータの表示方法を変更したいときはActiveRecordモデルのコードをイジるだけで済んじゃったり。
短期的な開発効率が上がるので爆速でMVPをリリースできるし、スタートアップの開発に向いている。
- Railsの欠点
テーブルとActiveRecordモデルが一対一の関係にあるため、例えばUserに関するデータ・振る舞い(CRUD操作も含む)・制約などがActiveRecordのUserモデルに集約されがち。
=> Userというモデルは用途やユースケースに応じて持つべきデータ・振る舞い・制約が異なる可能性があるのに、ActiveRecordのUserクラスにあらゆるユースケースの処理を生やしていくと1つ1つのメソッドがどのユースケースで使われるべきか分かりづらくなる。例えばnameプロパティにバリデーションをかける場合もユースケースによって制約内容が異なるかもしれない。
ActiveRecordは特定の1つのユースケースに特化したコードを書くことには向いているが、複数のユースケースに対応するコードを書く場合(テーブルとモデルが一対一とならない場合)に苦労する。Active RecordモデルがControllerやViewに渡りやすいことから、あらゆるレイヤーがActiveRecordと密結合な状態になってしまう。
=> 1とつながる内容ですが、アプリケーションが複数のユースケースを持ち始めた場合に問題が生じてくる。複数のユースケースと密結合なActiveRecordモデルのオブジェクトがあらゆるユースケースのViewやControllerに渡ってしまうと「ユースケースAではどのデータ(プロパティ)、どのメソッドを用いるのか分からない、、」ってなるし、変更漏れが発生しそう。
そして複数のユースケースのロジックを1つのActiveRecordモデルに記述すると可読性が落ちるし責務の大きい肥大モデルが生まれてしまう。
つまり、RailsのActiveRecordには
- ビジネスロジックの記述
- ビジネスロジックを組み合わせたユースケースの組み立て ( Active Record コールバック で副作用を実行することで一連の処理を組み立てる or 一連の処理をメソッドにする。 Railsでやってしまいがちな保守性を下げてしまうコードとその解決策にある通り、Formオブジェクト、サービスクラスとかも用いるか)
- 入力値バリデーション( Active Record バリデーション )
- DBアクセス
などの処理が集約されているため、複数のユースケースに考慮したコードが書き辛い。
この辺りはRuby on Railsの正体と向き合い方 / What is Ruby on Rails and how to deal with it? - Speaker Deckがめっちゃ分かりやすかったです。
以下に一部引用文を抜粋。
Railsはいつ、なぜ限界を迎えるのか?
・いつ:あるModelが複数の異なるユースケースでC(R)UD操作されるようになったとき。
・なぜ:あるModelに書かれたValidations/Callbacksは特定のユースケースと密結合しているため。
・何が辛いのか:あるユースケースに特化したModelの中で、別のユースケースの事情を考慮したコードを書かなければならないこと。
じゃあRailsで複数のユースケースにどうやって対応するの?
RailsやLaravelで複数のユースケースに対応した長期的な開発を取り組むためにDDDを実践したい場合は、Active Recordモデル(Eloquentモデル)をそのままViewやControllerに返すのではなくユースケースに沿ったドメインオブジェクトを返してあげるのが良い。
そしてInfra層のリポジトリ実装クラスでは、Active Record(Eloquent)のORMのみを参照する(DDDではDomain層のRepositoryでリポジトリのインターフェイスを定義し、Infrastructure層で永続化の処理を行うので)。
そして他のどのレイヤーからもActiveRecordの機能を参照しないのが良さそう?参照するならインターフェイス噛まして隠蔽するのが良いんかな。(Railsのインターフェイスってどんなやったっけ)
- ドメイン駆動設計の比類なきパワーでRailsレガシーコードなど大爆殺したるわあああ!!! - Qiita
- Laravelでドメイン駆動設計(DDD)を実践し、Eloquent Model依存の設計から脱却する - Qiita
おわりに
そもそもRailsの長所は短期的に爆速で開発できることであって、大規模開発には向いてないみたい。
でもRailsライクで生まれたCakePHPはCakeのエンティティ(DDDのエンティティとは別)と密結合になりがちやし、LaravelもEloquentモデルと密結合になりやすいから、Railsに限らん話では?と思った。