今後に向けてRuby on Railsのアーキテクチャ方針を話し合いました

f:id:estie:20210407014902p:plain

こんにちは!estieでソフトウェアエンジニアをしています「とーむ」です。

今回は簡単な自己紹介とestieでアプリチームが行っているアーキテクチャ規約に関する話し合いの内容を共有したいと思います。

自己紹介

平田東夢(ひらたとうむ) 1997年埼玉県出身。
東京大学大学院 情報理工学系研究科 在学中。専門は動画像処理や機械学習で、現在は自己教師あり学習手法を利用した「ノイズに影響を受けづらい動画認識モデル」について研究している。学部時代は体育会ラクロス部に所属しており、当時は体が鈍るのを恐れて逆立ちで作業をしていた。
2018年9月よりestieで働いており、今はestie proの開発をしている。

f:id:estie:20210407014103j:plain
逆立ちしながらプログラムを考える様子

estieとの出会いは2年半前、大学の友人に紹介してもらったゼミOBが社長の平井さんでした。その頃のestieはまだ登記すらしておらず、本当に立ち上げのタイミングだったので、はじめはモックアプリを作り、その後も主にアプリエンジニアとして働いています。 現在はestie proのアプリ開発と一部PdM業務も行っています。

背景

estie proでは、サーバーサイドをRuby on Rails(以下RoR)、フロントエンドをVue.jsで構築しています。RoRはMVCのアーキテクチャで構成されたフレームワークであり、フレームワークが独自の強い規約を設定します。こうした規約は開発者に規律を与え、一貫性のあるコードを書く手助けになりますが、アプリケーションが大規模になるにつれてRoRの規約にないロジックが増えてきます。estie proでもこうしたRoRの規約にないロジックが増え、コードベースの一貫性が下がっていたため、独自の規約を作成しようと目論みました。

目指すゴール

大規模アプリケーションのアーキテクチャではレイヤードアーキテクチャやクリーンアーキテクチャなどが有名ですが、これらをそのまま適用するのはRoRのフレームワークの規約やActiveRecordとコンフリクトする部分が多く難しいです。そのため、双方の良いところをバランスよく取りながら独自の規約を運用することに決めました。以下が現在考えているゴールとなる設計です。

設計の話し合いには『パーフェクト Ruby on Rails』の著者igaigaさんにも参加していただきました。詳細は以下の記事をご覧下さい。 inside.estie.co.jp

  • Controller

    • ユーザのリクエストをファサードになるUsecaseに渡す層
    • Usecaseの実行とPresenterの実行を責務として持つ
    • 依存先はPresenter、Usecase
  • Usecase

    • Serviceの受付部分
    • Service、Modelを操作し、やりたいことを達成する層
    • 依存先はService、Model
    • クエリ発行の主体
    • input、outputを明示的にコメント等で残す
  • Service

    • 複数のModelにまたがる処理やlogicをまとめたもの
    • それぞれのServiceが責務を持ち、閉じた設計となるよう気をつける
    • 依存先はModel
    • ここでもクエリが発行される
  • Model

    • ActiveRecordモデル
    • 単一のModelで閉じるロジックに関してはここに書かれる
    • 他のModelのメソッドを呼び出すなどの作用はここではなくServiceに置く
  • Presenter

    • viewに必要な形に整形する層
    • Usecase層のoutputとviewの都合に依存
    • 基本的にPOROの操作

Serviceクラスのinput/outputにActiveRecord::Relationを継承したクラスを許容するかや、PresenterがModelクラスに依存することを許容するかなど、まだ決定しきれていない論点は残っていて、こうした内容はリファクタリングを進めながら適宜話し合いアップデートしていく予定です。

リファクタリングのロードマップ

一旦ゴールの設計は考えましたが、一気に全てのリファクタリングをするのは工数的にもリスク的にも現実的でなく、以下の5つのphaseに分けてインクリメンタルにリファクタリングを行う予定です。

  1. リクエストテストの充実化
  2. Modelやcontrollerに入っている処理のService、Presenterとしての切り出し
  3. 整理されたModel、Serviceの単体テスト作成
  4. Usecaseの分析とレイヤ作成
  5. in/outの定義をClass化し、レイヤを分割

Phase1: リクエストテストの充実

現状のリクエストテストは仕様を網羅できていないのでここを充実させていきます。リファクタリング前に仕様の担保を行います。

Phase2: Modelやcontrollerに入っている処理のService、Presenterとしての切り出し

いわゆる”キャラの立った”サービスを切り出していくphase。in/outのClass定義はまだ行わず、明確にレイヤが分離されているわけではないが、責務としては分離されている状態を目指します。

Phase3: 整理されたModel、Serviceの単体テスト作成

Phase2に準ずるphase。各POROクラスにUnit Testを追加しながら、Phase2の設計を見直すphaseになると思われます。

Phase4: Usecaseの分析とレイヤ作成

ここまでのphaseで存在しているレイヤはPresenter、Controller、Service、Modelの4つです。Controllerが複数のServiceとModelを操作しPresenterのInputとなるPOROを整形する責務を持つことになります。本来これらの責務はUsecase(ApplicationService)層が持つべきなので、ここでアプリケーションの分析を行いUsecaseレイヤを作成します。

Phase5: in/outの定義をClass化しレイヤを分割

各レイヤ間の入出力を明確にPOROのClassとして定義します。(ここまでやるのはtoo muchかもしれないと思っています。)

現状は上記の規約のたたき台を見直しながらphase1に入っている状況です。

最後に

estieでは巨大かつ複雑な不動産ドメインのモデリングにチャレンジしています!不動産をアップデートしていくプロダクトを一緒に設計・開発をしてくださるエンジニアを募集しています! herp.careers

herp.careers