ryota21silvaの技術ブログ

Funna(ふんな)の技術ブログ

これまで学んだ技術の備忘録。未来の自分が救われることを信じて

Railsの利点と欠点、RailsがDDDや大規模開発に向いていない理由

知り合いが「Railsの長所ってなんやっけ?」って話をしてたので、その辺のRailsの長所・短所とか、RailsがDDDや大規模開発に向いてない理由を自分なりに整理してみた。

間違っている点などあれば是非ご指摘頂きたいです。


※2023/07/21 追記:
本記事においてユースケースという言葉を多用していますが、文脈(コンテキスト)という言葉を使用すべきだったかもしれません🙏
DDDにおいてモデルはユースケースごとに分かれるのではなく文脈ごとに分かれるものであり、本記事でも文脈というニュアンスでユースケースという言葉を使用していましたが、適切な言葉を選ぶべきでした。
ex. 商品というモデルを販売コンテキスト、配送コンテキストに分ける(参考: 境界づけられたコンテキスト 概念編 - ドメイン駆動設計用語解説 [DDD] - little hands' lab)。


Rails(Active Record)の利点と欠点

RailsというかActive Recordの利点と欠点を話しているだけ。

  • Railsの利点
    1. RDBのテーブルとActive Recordのモデルが一対一になり直感的でわかりやすい。CRUD機能、バリデーション、ロジックなどをすべてActive Recordモデルに書けるので短期的には爆速で開発できる。
    2. これはORM全般に共通する利点なので蛇足ですが、オブジェクト(モデル)を直感的に操作するだけでDBアクセスする処理を書ける。生SQLを直接書かずに済む(どんなSQLが発行されているかは確認しないとですが)

 つまりViewやControllerなどあらゆるコードがActiveRecordと密結合になるので、例えばViewに渡っているデータの表示方法を変更したいときはActiveRecordモデルのコードをイジるだけで済んじゃったり。 短期的な開発効率が上がるので爆速でMVPをリリースできるし、スタートアップの開発に向いている。

  • Railsの欠点
    1. テーブルとActiveRecordモデルが一対一の関係にあるため、例えばUserに関するデータ・振る舞い(CRUD操作も含む)・制約などがActiveRecordのUserモデルに集約されがち。
      => Userというモデルは用途やユースケースに応じて持つべきデータ・振る舞い・制約が異なる可能性があるのに、ActiveRecordのUserクラスにあらゆるユースケースの処理を生やしていくと1つ1つのメソッドがどのユースケースで使われるべきか分かりづらくなる。例えばnameプロパティにバリデーションをかける場合もユースケースによって制約内容が異なるかもしれない。
      ActiveRecordは特定の1つのユースケースに特化したコードを書くことには向いているが、複数のユースケースに対応するコードを書く場合(テーブルとモデルが一対一とならない場合)に苦労する。

    2. Active RecordモデルがControllerやViewに渡りやすいことから、あらゆるレイヤーがActiveRecordと密結合な状態になってしまう。
      => 1とつながる内容ですが、アプリケーションが複数のユースケースを持ち始めた場合に問題が生じてくる。複数のユースケースと密結合なActiveRecordモデルのオブジェクトがあらゆるユースケースのViewやControllerに渡ってしまうと「ユースケースAではどのデータ(プロパティ)、どのメソッドを用いるのか分からない、、」ってなるし、変更漏れが発生しそう。
      そして複数のユースケースのロジックを1つのActiveRecordモデルに記述すると可読性が落ちるし責務の大きい肥大モデルが生まれてしまう。

つまり、RailsActiveRecordには

などの処理が集約されているため、複数のユースケースに考慮したコードが書き辛い

この辺りは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の長所は短期的に爆速で開発できることであって、大規模開発には向いてないみたい。
でもRailsライクで生まれたCakePHPはCakeのエンティティ(DDDのエンティティとは別)と密結合になりがちやし、LaravelもEloquentモデルと密結合になりやすいから、Railsに限らん話では?と思った。

その他参考資料