ちょっと話題の記事

Railsの設計に迷ったのでGitLabの設計ドキュメントを読んでみた

Railsのプロジェクトがそこそこ大きくなり、ServiceやSerializerなどのカスタムレイヤーを追加してコードを細分化しているものの、レイヤーの役割やインターフェイスのルールが明確に決まっておらずふわふわとしていることを課題と感じていました。課題を解決するヒントを探すため、Railsの超巨大OSSプロジェクトであるGitLabの設計ドキュメントを読んでみました。

ガイドラインの必要性

まず初めにガイドラインの必要性が語られています。レイヤーの抽象化ができたとしても、それを正しく使えないと、あっという間にメンテナンスしにくいコードができてしまうということが説明されています。

例として、あるFinder(Finderはデータベースからデータを検索する抽象)の中で別のFinderを呼び出してはいけないということが挙げられています。もしそうしたなら、Finderにどんどんオプションが追加されて条件分岐が膨大になると警告しています。

たしかに、私が関わっているRailsプロジェクトでも、Serializer(レスポンスのデータ構造を整形する抽象)の中で別のSerializerを再利用して呼び出しており、コアなドメインのSerializerだとオプションが10個近くになっていて、バグが起こりやすい状況になっていました。しかし、完全に再利用しないとなると、各Serializerに似たような(あるいは完全に一致する)コードが多くなり、これはこれでバグが起こりやすい状況になるので、悩んでいるところでした。

GitLabでは、抽象の呼び出しの向きが明確に定義されており、これによるとSerializerからSerializerを呼び出すことはNGとされていました。

Serviceクラスの定義

ServiceはRailsではお馴染みのレイヤーですが、その役割は様々な印象です。GitLabでは、Serviceは「アプリケーションの状態に変化を加えるもの」と定義しているようです。さらにインターフェイスとして、コンストラクタの引数、唯一のインスタンスメソッドで処理を実行すること、戻り値の構造などが明確に決められています。

基本的には、Controllerはモデルを操作するようなビジネスロジックを持たず、Serviceを呼び出すだけになるように設計されているようです。たしかに、Serviceに切り出す大きさに迷うくらいなら、潔くビジネスロジックはServiceに書くと割り切るのがいいのかもしれないですね。

Serviceの戻り値がHTTPレスポンスのステータスコードまで含まれているのはレイヤーの境界としてどうなのかなとも思いましたが、エラーハンドリングの実装を省くための現実的な回答なのかもしれません。

おわりに

今日は毎年恒例の創立記念日エントリーということで、調査中の内容を駆け足で紹介しました。Railsプロジェクトのリファクタリングが完了したら、before/afterのアンサーブログを書きたいと思います。