【第1回】Railsで学ぶソフトウェアアーキテクチャ【アーキテクチャの基礎】

はじめに

はじめに、これを書くことになった動機の話を少しだけ。私は普段Ruby on Railsで開発をしていることが多いです。ある時、チームに新しくエンジニアがJOINして下さったときに全員がRailsについて詳しいわけではなかったので、Railsでの開発におけるアーキテクチャや設計の意識を揃えられたら良いなと思い勉強会を始めたのが始まりでした。ソフトウェアアーキテクチャの勉強会では基本的に私が学んできたことを体系的に整理してスライドで発表するような形でチームの皆さんに聞いて頂く形だったのですが、予想以上に嬉しい感想を毎度毎度頂くことになりました。そしてこれは自チームだけでなく広く色々な方に届けられた方が良いのかもしれないと思い改めて文章としてまとめて記載しております。実際の勉強会では開発しているプロダクトのコードを用いて解説をすることで、本を読んだ時のような単なるアーキテクチャの勉強で終わらせず、実際に開発するときにどうしたら良いかを一緒に考えられるように工夫しました。明日から役立つ知識としてまとめたことが特に上手くいった要因だと思っているため、このブログではそこまで出来ないことがやや歯がゆいですが、Railsで開発をしている方はぜひ自身のプロダクトにあてはめつつ見ていただけると良いかもしれません。ではこれからRailsを題材にソフトウェアアーキテクチャについて解説していきます。※私の個人的な解釈なども入っているので正確でない部分もあるとは思いますがご容赦ください。

アーキテクチャの基礎

 Railsのアーキテクチャに入る前に基礎的なアプリケーションアーキテクチャについて話します。

3層アーキテクチャ

 アプリケーションアーキテクチャには、古くから関心ごとによって層を3つに分ける「3層アーキテクチャ」というものがあります。現在ではクリーンアーキテクチャやオニオンアーキテクチャなど色々な形のアーキテクチャについて耳にすることも増えましたが全てが3層アーキテクチャの発展系と言うことができます。

 つまり3層アーキテクチャは最も土台となるもので、一度学んでおけば応用が効くものになっています。まずはこれを理解していきます。

 3層アーキテクチャの3層というのはそれぞれ

  • 「プレゼンテーション層」
  • 「ビジネスロジック層」
  • 「データアクセス層」

になります。端的に言えば、「見た目」と「ロジック」と「データ」を分離したアーキテクチャです。

 1つ目のプレゼンテーション層は入出力周りに関するコードを書くところです。CLIの入出力やGUIの画面に関する実装などがこれにあたります。

 2つ目のビジネスロジック層はアプリケーションの核となるコードを書くところです。核というと抽象的ですが、具体的な話をすると例えばじゃんけんのアプリケーションを作るとなった場合、じゃんけんの勝敗を決めるロジックがまさにこの核となります。プレゼンテーション層とデータアクセス層に書かないことはこのビジネスロジック層に書く、という反対の捉え方をしても分かりやすいかもしれません。

 3つ目のデータアクセス層は、永続化に関するコードを書くところです。具体的にはデータベースとやりとりする処理を書くところになります。まとめると以下のようになるでしょう。

3層に分けることの意味

 3層に分けるということを説明しましたがこれは何も概念だけの話をしているわけではありません。実際のコードも具体的に分けてやらなければいけません。そこでコードを分ける方法がディレクトリを分けることです。各層ごとにディレクトリを作ってやることで、このディレクトリはプレゼンテーション層の処理が書かれている、こっちのディレクトリはビジネスロジックが書いてあるといったように一目でわかるようになります。

 もしかしたら3層よりもっと層を分けた方が良いと思うかもしれません。それは実際ほとんどの場合で正しく、3層ではざっくり分けすぎているためアプリケーションが複雑になるにつれ困る部分も出てきます。そうして層が増えて発展していったのがクリーンアーキテクチャやオニオンアーキテクチャなどと言えます。

依存の向き

 アーキテクチャで大事なのが層の概念ともう一つ、依存の向きです。依存とは何かはここでは省略します。依存の向きが大事な理由は「このコードはここからしか呼び出されない」ということが明確に決められるためです。あるコードがあらゆるところから呼び出されていると変更が大変になってしまいます。つまり依存の向きというのはコードに秩序をもたらしてくれていることになります。3層アーキテクチャの依存の向きは下図のようになっています。プレゼンテーション層はビジネスロジック層に依存する、ビジネスロジック層はデータアクセス層に依存する、といった具合です。

 アーキテクチャにおいて依存の方向が決まっているだけではなく「何に依存するか」というのも重要な話です。「具体より抽象(安定している方)に依存せよ」といったような原則や話もありますが話が広がりすぎてしまうのでここでは説明を省きますのでご了承ください(いずれ説明したいと思います)。気になる場合は各々調べてみてください。

 

3層アーキテクチャとRailsの位置付け

RailsはMVCアーキテクチャ

 RailsはMVCというアーキテクチャが用いられています。図にすると以下のようになります。リクエストに対してコントローラが入出力の役割を担います。まずコントローラからモデルに対してデータの処理を依頼してModelが実際にデータの処理を行います。そしてその結果を用いてビューに画面の構築を依頼します。そうして作られたHTMLをレスポンスして返す仕組みです。ちなみにRailsのAPIモードはこのうちビューがなくなったものという認識で大丈夫です。

3層アーキテクチャに当てはめる

 MVCアーキテクチャは図や用語が3層アーキテクチャとは全く異なっていますが、実は3層アーキテクチャに帰着することができます。3層アーキテクチャに帰着させたのが以下の図です。

 

 コントローラとビューは入出力・表示に関する内容を扱っているのでプレゼンテーション層に当てはまります。そしてモデルはロジックを書くのが一般的なのでビジネスロジック層だと言えるでしょう。そしてデータアクセス層に当てはまるものがないことが気になるところですが、ここはRailsのActive Recordによって隠蔽されています。具体的な話をすると、Railsで我々がモデルを作る時必ず「ApplicationRecord」というクラスを継承して作ります。これによってデータアクセスに関する処理(SQLでいうSELECT・INSERT・ UPDATE・ DELETE文)を自ら書く必要がなくなっています。つまりデータアクセスに関する処理はRailsのフレームワークが既に用意してくれているのです。我々が実装するMVCの部分では基本的にデータアクセスの具体的な処理を意識しなくて良いようになっているため、データアクセス層には何も当てはまらないということになります。(補足すると、複雑な処理が必要で自分でSQLを書くというようなケースも実際にはありますが、今は全体感の話をしているのでそういう細かい事情は無視しています。)

ビジネスロジックは2つに大別できる

 実はビジネスロジック層が扱うビジネスロジックというのは、大別すると2つの意味のコードを抱えることになります。

 それが「コアなルール」と「ユースケース」です。コアなルールの方はドメイン駆動設計の文脈ではドメインロジックと呼ばれたりするものですが、話を広げると大変になるので簡単のためここでは一旦コアなルールと呼んで話を進めます。

コアなルール

 コアなルールとは何かを具体的な例で理解します。例えばECサイトを開発しており、商品モデルというのを作ったとしましょう。そこには色んな制約や振る舞いがありそうです。例えば

  • 「商品名と商品説明カラムは入力必須」という制約
  • 「特定のジャンルの商品かどうか判定する」という振る舞い
  • 「商品が販売中かどうか判定する」という振る舞い

が必要だとしましょう。それをコードとして書くと以下のような感じで書けそうです

こういったモデルに関する制約や振る舞いは、「コアなルール」として扱います。

ユースケース

 ユースケースも先のECサイトの具体例で理解します。例えば、商品購入ボタンを押した時に次のような一連の処理が必要だったとしましょう

  • 1. 商品の在庫を購入分だけ減らす
  • 2. 購入履歴のデータを作る
  • 3. 決済処理を行う

このような切り離せないまとまった一連の処理がまさに「ユースケース」にあたります。他にもメールを送るとか、ログを残すとかもあるかもしれません。これをどこに書くのが適切か考えてみましょう。

 まずは最も思いつきそうなのがコントローラーに書くパターンです。

ですが思い出してください、コントローラはプレゼンテーション層に位置するので、ビジネスロジックであるユースケースを直接書くのは関心の分離がちゃんとできていないことになります。これまでのセオリーではビジネスロジックはモデルに書くということだったので今度はモデルに書いてみましょう。すると次のようになりそうです。

モデルにビジネスロジックが書けたのでこれでOKでしょうか。違和感を感じる部分はないでしょうか。今回だと、在庫モデルと購入履歴モデルの2つのモデルを扱った処理なので、どっちのモデルに書くのか迷いが生じることでしょう。そして決算処理をモデルに書いてしまうことにも違和感を感じるかもしれません。また今後こういったユースケースを全部モデルに実装していくとなると、モデルが巨大なものになっていくことにも悩まされることになります。

 つまり、ビジネスロジックのうち「ユースケース」はモデルで書こうとすると早い段階で限界が出てくることになるのです。

(補足)先ほどユースケースをコントローラに書くのは関心の分離ができていないと述べましたが、イコールで絶対にダメというものだとは思っていません。小さなアプリケーションでロジックが少ない場合や、ユースケースのロジックが非常に簡単なものであるような場合は、コントローラに書く場合もあるでしょう。色々検討した結果コントローラーにユースケースを書くという意思決定があってもそれはOKだと考えています。

4層アーキテクチャへ

Railsはユースケースを書く場所を用意していない

 2.3.にてユースケースを書く場所に困るという話はしましたが、解決策の話はしていませんでした。それもそのはず、Railsにはユースケースを書く場所は最初から用意されていないためです。従って、ユースケースを書く場所が欲しくなった時、自分達で用意しなければなりません。ここで1つ、Service Objectというユースケースを扱うためのよく知られたデザインパターンを利用して(以下画像でのPurchaseServiceクラスがService Objectになります)先ほどの例の商品購入のコードを書き直してみます。

PurchaseServiceというユースケースを責務として扱うクラスを追加したことで、モデルはビジネスロジックのうちコアなルールのみを責務とし、Service Objectはビジネスロジックのうちユースケースを責務とするように上手く関心の分離をすることができました。

4層アーキテクチャへ

 ビジネスロジックにはコアなルールとユースケースがあり、これらはまとめてモデルで扱うにはやや大変だということが分かりました。なので分割した方がよさそうですね、ということでビジネスロジック層を分割し、3層アーキテクチャを4層に拡張しましょう。

 一般的によく使われる名称を用いて、ユースケースを扱う層を「アプリケーション層」、コアなルールを扱う層を「ドメイン層」と設定しました。ドメイン層はMVCのモデルが担うことになりますが、アプリケーション層はどのようにしたら良いでしょうか。先ほど導入したService Objectというデザインパターンだけなのでしょうか。またService Objectで困る事はでてこないのでしょうか。

 こういったRailsが用意したMVCから外れて運用していくことは、各企業がさまざまな知見を共有してくれており、デファクトのようになってきたものも多くあります。例えばService Objectはまさによく使われてきたものです。他にも、ユースケースと一口にいっても色々な特徴を持つものがあり、例えば複雑なクエリを扱うためのQuery Objectや複雑なフォームを扱うためのForm Objectといったさまざまなデザインパターンも考案されてきました。

 実際に私が普段開発するプロダクトでもこういったいくつものデザインパターンが利用されています。またユースケース以外のプレゼンテーション層などでも目的に応じてデザインパターンが用いられていたりもします。こういったデザインパターンを必要に応じて使いつつより変更しやすいアプリケーションを作っていけると良さそうですね。今回はここまでとなります。次回からこの4層アーキテクチャを元にしつつ、よく使われるデザインパターンがどのような責務を持つのかを解説していきます。

コメント

タイトルとURLをコピーしました