はじめに
Ruby on RailsでAPIの開発を行ってるんですが、Railsは開発者に優しくとても開発がしやすいです。一方、どこからでもModelが呼べたりと人によって書き方もバラバラになりやいフレームワークでもあります。
とはいえ、Rialsをクリーンアーキテクチャで設計して開発を進めていくのは、Railsの良さをなくしちゃうのであまり相性が良くないと思います。そこで、Railsの既存のMVCを崩さない範囲でルールを決めて開発を行うようにしました。
開発はRailsの6系で、APIモードで開発を行っています。
考えた各層の役割
最終的に各層の役割は以下のように定義しました。
役割、詳細 | |
---|---|
Controller | パラメーターなどを取得し、Service層とPresenter層を呼び出す役割。ビジネスロジックは持たず、基本的に呼び出しのみを行う。 |
Model | バリデーションや(サービスではなく)ドメインに依存するロジックを持つ役割。joinやwhereなどactive recordを操作するようなものはscopeで定義し、それ以外のドメインに依存するロジックは関数で定義する。また、基本的にModel側は呼び出されることしかなく、呼び出し側に依存した処理は書かない。 |
Service | 基本的にcontrollerから呼び出され、サービスにおけるビジネスロジックを持つ役割。また、テーブルの結合などもここで行う(結合のためのロジックはModelで持っても構わない)。 |
Presenter | APIにおけるjsonの返却値などを作成する役割。基本的に呼び出される役割でロジックなども持たないが、他のPresenter層やDecodater層、Model層で定義された関数(scopeは禁止。defで定義されたもののみ)を呼び出すことが可能。 |
Decorater | 表示部分のロジックを持つ役割。ビジネスロジックではない表示部分のロジックのみであれば、こちらで保持する。 |
この方針にした時に特に考えたこと
- ロジックをも持つ層を明確にしたかったので、ドメインに依存するロジック(Model層)、ビジネスロジック(Service層)、表示部分のロジック(Decorater層)に分けて定義しました
- N+1問題を起こりにくいようにしたかったので、結合などは全てSerive層に閉じ込めました
- Modelで定義する関数は、active recordを操作するものは
scope
で定義、それ以外にロジックだけ持つものはdef
で定義し、呼び出し側のルールも決めました。これにより上記のように結合場所をService層のみに閉じ込めやすくしました
さいごに
ルールを決めるまでは結構書き方がバラバラになりやすかったのがルールを明確にすることで統一され、またN+1問題の解消にも繋がりました。
もちろんこれはあくまで自分たちのルールなので、正解とかありませんしサービスの規模感によっても変わると思いますがすこしでも参考になればと思います。