こんにちは、Webエンジニアの本間です。 最近、Railsアプリケーションのディレクトリ構成に関して考える機会があったので、そのことについて書いてみようと思います。
Railsに限らず「ディレクトリ構成をどうするか」という点は、設計初期の悩ましい問題の1つだと思います。 Railsでは標準のディレクトリ構成が決まっているため悩みは少ないのですが、多くの場合、標準外のディレクトリもある程度は追加していくと思います。 特に「app」以下のディレクトリ構成は、アプリケーション全体の設計に関わる部分であり、各メンバーの考えもあるため、なかなか決まらないケースもあると思います。
この問題に対する1つの参考情報として、著名なOSSのRailsアプリケーションが採用しているapp直下のディレクトリを調べてみようと思いました。 著名なOSSで採用されているのであればそれなりの理由があるはずだと思いますし、世の中の標準にも近いはずなので、あとから参加したメンバーも違和感なく開発できると考えたためです。
調査
以下の条件で「Railsを使用している著名なOSSプロジェクト」を集めました。
- Githubでコードが公開されている
- 主要言語がRuby
- Star数1000以上
- Railsアプリケーションである
- 今年に入って一度も更新されていないリポジトリは除外
- リポジトリ直下に
appディレクトリがないリポジトリも除外
最終的に以下の15個のリポジトリを対象にしました。
| No. | オーナー名 | リポジトリ名 | URL |
|---|---|---|---|
| 1 | discourse | discourse | https://github.com/discourse/discourse |
| 2 | gitlabhq | gitlabhq | https://github.com/gitlabhq/gitlabhq |
| 3 | huginn | huginn | https://github.com/huginn/huginn |
| 4 | diaspora | diaspora | https://github.com/diaspora/diaspora |
| 5 | tootsuite | mastodon | https://github.com/tootsuite/mastodon |
| 6 | errbit | errbit | https://github.com/errbit/errbit |
| 7 | fatfreecrm | fat_free_crm | https://github.com/fatfreecrm/fat_free_crm |
| 8 | edavis10 | redmine | https://github.com/edavis10/redmine |
| 9 | Netflix | Scumblr | https://github.com/Netflix/Scumblr |
| 10 | locomotivecms | engine | https://github.com/locomotivecms/engine |
| 11 | rubygems | rubygems.org | https://github.com/rubygems/rubygems.org |
| 12 | opf | openproject | https://github.com/opf/openproject |
| 13 | houndci | hound | https://github.com/houndci/hound |
| 14 | loomio | loomio | https://github.com/loomio/loomio |
| 15 | jcs | lobsters | https://github.com/jcs/lobsters |
上記のリポジトリの app の直下にどういったディレクトリがあるか、集計してみます。
結果
2017年8月1日に実行した結果は以下になります。「リポジトリ数」が「2」以下のものは結果から除外しました。
| ディレクトリ名 | リポジトリ数 |
|---|---|
| controllers | 15 |
| helpers | 15 |
| models | 15 |
| views | 15 |
| mailers | 14 |
| assets | 13 |
| services | 8 |
| jobs | 7 |
| serializers | 7 |
| policies | 5 |
| presenters | 5 |
| workers | 5 |
| uploaders | 4 |
| validators | 4 |
Rails標準のディレクトリが多いですが(でもchannels入ってないな…)、 services や serializers など多くのリポジトリで使用されているディレクトリもあります。
次にRails標準で作られないディレクトリに入っているクラスの役割を、各リポジトリのコードをざっと読んで確認してみます。
services
過半数のリポジトリで使用されているディレクトリで、ある程度のロジック(複数Modelの更新、Jobの登録、外部サービスとの連携など)を取りまとめるクラスが格納されるようです。 ControllerやJobなどでこれらのクラスを使用することで、ロジックの重複を排除するほか、Controllerクラスが肥大することを防ぐ目的で使用されていると考えられます。
クラスの書き方に関しては、各リポジトリで微妙に異なっており興味深いです。
- インスタンスメソッドとするか、クラスメソッドとするか
- 処理実行に必要なパラメータを
#initializeで渡すか、対象のメソッドを呼び出す際に渡すか - サービスクラスのpublicなメソッドは1つのみ限定とするか、複数メソッドを許容するか
以下は、mastodonのServiceクラスの例です。mastodonでは、「インスタンスメソッド」「パラメーターはメソッド呼び出し時に渡す」「publicなメソッドは #call のみ」で統一されていうように見受けられます。
serializers
ActiveModel::Serializer 用のSerializerクラスを格納しています。15個のリポジトリ中7個で使われていて、このGemが広く使われいることがわかります。
policies
主に権限チェックのためのルールを記述したクラスが格納されるようです。格納されたクラスの使われ方ですが、半分は pundit 用のPolicyクラスです。もう半分は自前の権限チェックの実装で使用されているようです。gitlabhqのPolicyクラスが、独自のDSLを使って定義されているのが印象的だったので、以下に引用しておきます。
presenters
ModelオブジェクをラップしてView用に使いやすくするクラスが格納されるようです。Modelクラスのコードの肥大化を避ける目的があると考えられます。 細かい役割や利用方法はリポジトリによって異なるようです。
diasporaでは、View用のJSON変換の際、特定の属性のみに限定したり、少しデータを加工する目的で使用されているようです。一例は下記になります。
一方、gitlabhqでは特定のView用のロジックを持つものとして、より広い用途で使われているようです。専用のREADME が用意されており、メリット、使用すべき状況、helperやmodel concernsはなぜ使わないのか、などの説明が記載されています。
workers
sidekiq のWorkerクラスを格納しているケースが大半でした。
uploaders
CarrierWave で使用するUploaderクラスが格納されています。
validators
カスタムバリデーター 用のValidatorクラスを格納しています。
まとめ
今回、著名なOSSのRailsアプリケーションを対象にapp直下で使用されているディレクトリを調べてみました。
特定のGemが生成するディレクトリ( serializers、 workers、…)が多かったですが、 services や presenters といったGemに依存しないディレクトリが広く使用されていることを確認することができました。
ただし、Gemに依存しないディレクトリに格納するクラスは役割や書き方が曖昧になりがちなので、使う際はルールをちゃんと決めておいた方がいいなと感じました。
最後までご覧いただきありがとうございました。何かの参考になれば幸いです。