プログラムの構成要素をModel,View,Controllerに分割し、
保守性や生産性の向上を目指すMVCモデル。
実際のプロジェクトのソースコードでは、MとVの間に中間層が設けられているのをよく目にします。それらは何のためにそこに存在し、何をもたらすのでしょうか。
MVCモデルの概要と意義
今回の内容に入る前に簡単にMVCモデルについてかるく押さえておきます。
まずはWikipediaの記事を見てみましょう。
MVCはソフトウェアを処理/Model・表示/View・入力伝達/Controllerの3要素に分割し、ソフトウェア内部データをユーザーが直接参照・編集する情報から分離する。プレゼンテーション(View・Controller)とドメイン(Model)を分離しまたユーザー入力(Controller)と表示(View)も分離することでソフトウェアの保守性・開発生産性を向上させる。
https://ja.wikipedia.org/wiki/Model_View_Controller
この記述から、MVCモデルのいちばんの意義は、
「ソースコードを役割毎に分離することで、わかりやすい構造にすること」
と言えるでしょう。
それぞれの役割をざっくり定義すると、こんな感じです。
Model :データに対する窓口
View :ユーザーに対する窓口
Controller:Model,Viewに指示を与えること
Service層とRepository層はどこにある?
MVCモデルの概要について軽く押さえたところで、ServiceとRepositoryはMVCモデルのどこに位置しているのかを確認しておきます。
図を見てわかるように、ServiceとRepositoryはControllerとModelの中間に位置しています。
見出しで層という言葉を用いたのは、このように構造が階層構造になっているためです。
ControllerがModelに指示を与え、ModelからControllerにデータを返す。この伝達の間に2つの層が挟まっています。図を見るとさながらバケツリレーのようにも見えますが、CとMの間にバケツを持って移動するには大変な物理的な距離があるわけではありません。
Service層とRepository層はCとMの役割の一部を引き受け、より明確に分担しようとしています。
そもそもオブジェクト指向って何がいいんだっけ?
さて、ServiceとRepositoryの設置によって得られる恩恵に迫る前に、一度立ち止まってオブジェクト指向についてちょっとだけ、触れておきましょう。
オブジェクト指向の特徴として挙げられる3要素はこちらです。
・カプセル化(情報隠蔽) ※カプセル化と情報隠蔽は厳密には異なる概念ですが、ここでは深く立ち入らずまとめて扱います。
→関連するデータと処理をひとつにまとめ、他の層からアクセスする必要のない情報を隠蔽して使用することで、可読性・保守性の向上に繋がります。
・継承
→別のクラス(データと処理のまとまり)を引き継いで再利用することで開発スピードを上げ、可読性・保守性の向上にも繋がります。ひとつのクラスは複数のクラスに継承できるため、共通部分のみを継承元のクラスに記述することで、同じようなコードがたくさん生成されてしまうことを防ぐことができます。
・多態性(ポリモーフィズム)
→共通化したメソッド(処理)を継承先の子クラスでオーバーライド(上書き)して使用することで同じメソッドで異なる挙動を実現することができます。
MVCモデルや、今回扱う設計思想も、このオブジェクト指向のメリットをより引き出すものになっているので意識しながら見ていきましょう…!
共通点も多いが、目的が違う!でも思想は近い。
Service層の役割
2つの中間層のうち、Controllerに近い層に位置するService層の担う役割は、「Controllerの軽量化」です。
はじめの図でもわかるようにModelとViewの仲介役を担うControllerは、Viewから受け取ったリクエストをもとに処理を行うため、あらゆる処理を書くことができてしまいます。その結果、Controllerが肥大化し、どこに何が書いてあるかを理解するのに時間がかかる「ファットコントローラ」が完成してしまいます。
Service層の役割は、Controllerから”ビジネスロジック”を引き受けることです。これまでControllerに記述していた具体的な処理をひとつひとつServiceが引き受け、ControllerはServiceから呼び出したメソッドを組み合わせて一連の処理を形成することに専念することになります。これにより、Controllerが軽量化され、可読性・保守性を向上させることができます。
Repository層の役割
一方、Modelに近い層に位置するRepository層の担う役割は、「データアクセスの隔離」です。
Service層がControllerのビジネスロジックを引き受けたのに対して、Repository層はデータアクセスを引き受けます。
Repository層はModelの唯一の窓口となる層で、ここにデータアクセスの処理を集約させます。
データアクセス処理をRepository層に集約することで、上位の層に対してデータアクセスの処理の情報を隠蔽できる他、「思わぬところからデータをいじられてしまっていて問題箇所に気付くのが遅れた」などというトラブルを未然に防ぐことができます。
共通点と全体構造
Controllerを抽象化することで肥大化を防止したいServiceと、データアクセスを隔離することで保守性をあげたいRepositoryは出発点は違いますが、特定の役割を明確に引き受け、中間層として一直線の処理を形成するゴールが似ています。
そして、この二つの中間層ははじめに載せた図のように組み合わせて使用することで最大限の効果を発揮します。
まず、Controller→Service→Repository→Model という処理の流れで最も大切なことは必ず一直線に処理を行うということです。Serviceは直接Modelに働きかけてはいけないし、ControllerがRepositoryの処理を直接呼び出してはいけません。
このような処理を記述すると、「何がどこに書いてあるかが明確」「処理の流れを追いやすい」というメリットが「何をどこから呼び出しているか追えない」という厄介なデメリットに様変わりしてしまいます。
それぞれの層の役割をしっかりと認識し、書くべき場所と流れを厳格に運用することで、この設計パターンのメリットを最大限享受することができます。
設計思想は変わりゆくものということ
ここまで見てきたService層を含むリポジトリパターンは、LaravelをはじめとするMVCフレームワークなどで用いられる設計パターンのひとつにすぎません。
今回紹介した設計思想は、処理の速度を上げたりコンピュータの負担を下げるものではなく、「改修する時にコードを理解しやすい」「必要な部分だけを考えやすい」など人間のためのメリットをもたらすものです。
技術が進化・変化するのと同じように、こうした設計思想も現在進行形で進化・変化しています。Reactが近年オブジェクト指向よりも関数型の考えに近くなっているのがいい例でしょう。
また、リポジトリパターンのようなアーキテクチャは小規模なプロジェクトではかえって工数が増えるだけで役不足になってしまうこともあります。
プロジェクトの状況や技術の流行も加味してその時々の最適な設計パターンを採用することが大切です。