Railsの”モデル”は責務を持ちすぎでは無いだろうか?
RailsのActive Recordはエンティティ(テーブル)とロジックを1対1に縛って、1枚岩の”モデル”と呼ぶオブジェクトとして扱っている。単純な場合は1対1で、多少の複雑さであればActive Recordは非常に便利なAPIを提供してくれて、開発効率をグンと上げてくれる。
しかし、ある程度の複雑度が出てくるとドメインロジックはエンティティと1対1に対応しなくなるし、それを扱う側(Controllerなど)によってロジックが変わることもある。
単純にFat modelに従えば、どちらか一方のモデルのメソッドとしてロジックを隠蔽する。そのため、複数のモデルの操作をしているにも関わらず、様々なデータ操作の責務を1つのモデルが抱えてしまう。これをControllerやViewで扱う時には、操作するオブジェクトが直感的で無く扱いづらい。
(例えば、複数のエンティティのデータを複合した集計なのに、一方のモデルのメソッドから取ってくる場合、1つのフォームから複数のモデルに対してアクセスする場合。)
かといってControllerに書いたら、DRYではない。
この1枚岩になっているエンティティとドメインロジックを、切り離してそれぞれ別のオブジェクトにした方が良いのではないだろうか?
この問題についてずっと考えてきた個人的な結論としては、ModelとControllerの中間層にオブジェクトが介在して、このオブジェクトがビジネスロジックの責務を持つのが良いのではないか?と考えてきた。
ふとSQLアンチパターンを呼んだところ、まさにActive Recordパターンのこの問題について言及している章があった。(24章:マジックビーンズ)SQLアンチパターンで提示されている解決策としても、エンティティ=アクティブレコード, ドメインロジック=ドメインモデルとして分離することを推奨していた。
ところで、1枚岩のActive Recordの機能をバラして使おうとする(Active Modelなど)とActive RecordやRailsの恩恵が犠牲になってしまう。(文字通りレールから逸れている)
ここをうまく切り抜けるにはどうしよう…というところ。うまくRailsのAPIを吸収して、ドメインモデルを分離したい。
この辺りに取り組んだ事例などが知りたい…
追記(12/11)
a_suenamiさんから、アドバイス記事をいただきました!
他にも参考になる記事があったので紹介します。
肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)
はてなではMVCモデルにS(Service)という層を追加することで、役割の分担をしています。ただし、これはPerlで実装した場合の話で、本来のMVCモデルからかけ離れてきたRailsのMVCモデルとは違う話になるかもしれません。
なお、『ModelとControllerの中間層にオブジェクトが介在』というのは、既にMVCS(Model View Controller Service)モデルという形で提唱されているので、それについて調べてみると良いかと思います。
アドバイスありがとうございます!
下記のリンクのようなサイトをめぐって、様々なパターンがある中で、どれをどう採用していこうか悩んでいたところでした。
http://techracho.bpsinc.jp/hachi8833/2013_11_19/14738
ServiceパターンはControllerから見たロジックをまとめるのに近いイメージでしょうか。
あとは、Serviceとして切り分ける粒度の大きさをどの程度に考えるのかが難しいですよね。
大きく粒度だと、DB操作→外部API等への通知・メール配信といった操作の流れをまとめる。
細かい粒度だと、複数のエンティティへの操作のみを行う程度。
粒度の違うものをServiceとしてどんどん切って、Service内からServiceを呼ぶような設計がよいのでしょうか?