中規模Railsアプリのアーキテクチャ設計
Railsでアプリを作っていますか?設計に問題を抱えている、あるいは悩んでいませんか?もしそうであればこの記事が役に立つかもしれません。
アプリケーションが大きくなっていくに連れて、コードがカオスになってきたり、どこで何が起こっているのか追いにくくなってくるものです。いわゆる「ファットコントローラ」「ファットモデル」と呼ばれる問題ですね。ところで、どうしてこの問題が起こるのでしょうか。それはMVCアーキテクチャが常に完璧ではないからです。Railsはアプリケーションの立ち上げに関しては本当に簡単な方法を提供してくれています。素晴らしいです。単にMVCと呼ばれる構造に従ってコードを書いていけばアプリケーションを書けるようになるのです。ただ、前述のように、どこかのタイミングで設計の変更を余儀なくされていきます。もうあなたはそのフェーズにいるかも知れません。どうやって何を変更したら良いのでしょう。
ここでは、ひとつの解決方法として、中規模サイズのRailsアプリケーションにおけるストラテジーを紹介します。ここでの方法はDDD(Domein Driven Design)と呼ばれる手法をベースにしています。記事の最後に本を紹介しているので興味があればぜひ手にとってみてください。
Environment
コントローラ層
コントローラ層はすべてのリクエストを受け付けます。基本的に、この層のみがドメイン層を呼べます。この層は可能な限りシンプルに保たれるべきです。
def save @request.assign_attributes(assignable_request_params) if @request.save RequestFlow::ReceiveFromAPI.execute(@request, @request_token) render json: { result: 'success' } else render json: { result: 'failed' } end end
ここでの RequestFlow::ReceiveFromApi
はドメインです。
ドメイン層
これは最も重要な層のひとつです。ドメインは技術者のみでなく、ビジネスやカスタマーサポートの人にも理解可能な共通言語で名前付けしてください。このタイミングでみんなで話し合うのもいいですね。例えば私は SelectXXX
, SendXXX
, CreateXXX
のようにつけることが多いです。ここでEmailなどの通知のようなフックイベントを定義します。理想的にはドメインはプロセス(手順)であったほうがよいです。
module RequestFlow module Update module_function def execute(request, options={}) GeocodeJob.perform_later(request) unless request.geocoded? Pipedrive::Updator.update_parameters( PipedriveStage.hash[request.status], request.pipedrive_deal, request ) request.update_status end unless options[:skip_notification] == true # This is a Service Layer Notifier::Request.complete_updating(request_offer) end end end end
ドメインはモデルとサービスを呼ぶことができます。しかし彼らから"呼ばれる"のは禁止です。 ドメインは中で何が起こっているのか理解しやすいようにしましょう。そうすると、このような会話が生まれることが想定されます。
CS: ユーザーがリクエストを更新したとき、何がおきるんだっけ? IT: ドメインをチェックしてみるね。そのリクエストは位置情報を付与されて、pipedriveに情報を投げているね。その後アップデート完了という流れだね。
いい感じですね。
サービス層
ここはステートレスな層です。サービスはコマンド(命令)のようなものだと思ってください。例えば、geocode_setter
, locale_finder
, distance_calculator
などです。私の経験では Notifier
(通知者)をここに置くのは良いと思います。
class Notifier::Request def initialize(request, timing = :wait, skip_validation = false) end ... def complete_request klass = new(request) klass.notify_internaly klass.via_sms klass.via_email end def via_email # call ActionMailer end end
モデル層
サービス層とドメイン層のおかげで、モデル層は薄くできるようになったはずです。モデルは自身が何を表しているかだけに関心を持ちます。純粋なRubyクラスもどんどん作りましょう。ActiveRecord::Base
を継承してることは必須ではないですからね。pureなRubyクラスは綺麗なデザイン、理解しやすい設計に大いに役立つはずです。
バリデーション層
この層もモデル層を薄く保つのに役立ちます。もしRailsにおいてバリデーション層をどう実装していいか知らなければ検索してみてください。実装方法は簡単で、ActiveModel::Validator
を継承するだけです。 それから validates_with
などを実装しましょう。当然ですが、ここではバリデーションのみに関心を持ちましょう。
これが基本的な設計の考え方にになります。もちろん、実際のプロダクトではもっと複雑になるでしょうけど、いままで私が関わったプロダクトではこのやり方はスケーラビリティにおいてもうまくいきました。この設計で重要なのは「誰が誰を呼べるか」の「方向(矢印)」です。これが正しく守られていないと、またカオスに逆戻りです。
もしもっとこの設計の理解を深めたければ下記の本がおすすめです。
それでは、Enjoy coding!