はじまりの村のバックエンドモジュール分割
目次
さっさとバックエンドのモジュール分割について知りたい方は、「モジュール分割の種類」までどうぞ
はじまりの村
ゲームはやりますか?
RPGの主人公がスタートするはじまりの場所には、その時点では行けなかったり、敵キャラクターが強すぎて先に進めないなどの場所があります。
この場所は、ゲームを進め主人公のレベルアップしたりや特別な能力を獲得して、再度村に帰ってきたときに、訪れる場所です。敵キャラクターを攻略して、探索ができるようになったり、強力な武器が手に入るような仕掛けがあったりします。
この記事は、そのような位置づけの記事になります。今回の内容は、初学者には、難しく・実感として理解できるようには、もう少しの経験をつまないと身につかない内容となっております。
このブログでは、Webエンジニア初学者向けの技術やお仕事についての記事を投稿していく予定ですが、この記事の対象読者は、
- 0~1年生だと理解はできるが、実践で生かしていくことがまだ難しい
- 2年生前後で、今回の内容を理解し実際のコードに活かすことができる
ぐらいの内容となっております。
ですので、以降は用語・内容についても初学者の方は、理解できなくても現時点では大丈夫です。まだあなたは、はじまりの村から冒険に出たばかりだからです。横目でこの記事を流し見してもらい、日々の業務に全力で取り組んでください。
でも経験を積んで戻ってきたときに、またはじまりの村に戻ってきてください。きっとあなたの役に立つものがあるはずです。
よく採用するバックエンドモジュール分割
では、モジュール分割についてです。ここではWebのプロジェクトでよくある規模感のプロジェクト・アーキテクチャで、私が採用するモジュール分割方法についてご紹介します。今回は、バックエンドのモジュール分割が範囲となります。
想定のプロジェクト規模
- 案件概要:物流・営業・予約などの管理サービス
- 要件:C向けのサービスで、小規模のバックエンド開発あり
- 規模:小・中規模のWebのCRUDサービス
- 人員:管理者含め3人前後
- 期間:ディレクション~リリースまで、3ヶ月前後
- アーキテクチャ:PHP/Laravel、LAMP
ここでは、サーバーレスやフロントjavascriptゴリゴリのキラキラ開発やwordpressなどを使用したCMSのようなサービスではなく、フルスクラッチの開発を想定しています。
以降は、想定したアーキテクチャのPHP/Laravelを念頭において解説していきますが、他の言語でも参考にできる要素はありますので、適宜自身の慣れ親しんだ環境に読み替えてお読みください。
分割のことはじめ
最近のフレームワーク(FW)はよくできていまして、リファレンスの手順やフォルダ構成に従うだけど、いい感じにモジュール(≒クラスファイル)が分割されるようになっています。今回採用したLaravelでも例にもれず、ルーティング・コントローラ・モデルなど役割によって既にモジュールが分けられています。
その中でも、よりクラスを細分化して役割によって名前をつけます。そして、細分化クラスの呼び出しや参照にルールをつけることにより、各モジュールの使い方を決めていきます。
では、モジュールを紹介する前に少しだけ、分割のメリットとデメリットをお伝えしてから、細かい内容に入っていきます。
旨味と苦味
プロジェクトやクライアントの要望は様々で、一つとして同じプロジェクトはないでしょう。ですので、正解のコードや進め方があるというよりは、より良い選択や今後を見据えての、その時点で最善だと思われる選択をしていくことになります。
当然、モジュール分割の採用にも効果・副作用があり、メリットとしては
- 可読性の向上
- メンテナンスコストの低下
- モジュールの共通化や使い回し
- 複数人での同じ機能の並行開発
- モジュールの差し替え
などなどが挙げられます。特に、複数人で開発をする場合には、後発参画者の学習コストを含めても、メンバーの開発速度・ストレス軽減などの恩恵がより感じられます。
その逆に、デメリットとしては
- 学習コストの増加
- モジュールの細分化によるクラスファイルの数が増える
- エンジニアが一人だと、分割や共通化の恩恵がうけにく
- ルールの準拠などの縛りが窮屈
などでしょうか。
超小規模開発等・どこに何があるの把握できている・使い捨てや機能数がごく少数の場合は、分割にかかるコストの方が高くつきます(メリットが少ない)。
その際は、あえてしない選択肢もありです。ですが前述したプロジェクト要件の場合は、分割するケースが圧倒的に多いです。
分割は、クラスの見通しやあるべき場所にその機能がある・整頓されている等のメンテナンス時のスムーズさ複数人開発時のストレスの少なさをどのように評価するかで、導入するかしないかを検討するとよいです。
では、前置きはこのくらいにして本題に入っていきましょう。
モジュール分割の種類
以下に、今回採用するバックエンド分割方法のモジュールの種類について列挙します。
- Controller(コントローラー)
- Service (サービス)
- Repository(リポジトリ)
- Model(モデル)
- Presenter(プレゼンター)
の大きく分けて、5種類です。コントローラーとモデルは、Laravelの場合ディレクトリや使用用途のドキュメントが用意されており、そのままの役割となります。
いわゆる、MVCとかADRとかあのへんをフラフラしたようなモジュール分割方式です。用語や役割については、出身言語や技術背景によって齟齬があったりするので、詳しく書いていきます。
なお、表示部分をになうViewは、今回のモジュール分割の説明にはあえて含めていません。
レスポンスの返却を行うところまでを、説明の範囲とします。Viewは返却形式やアーキが違うものの、データ受け取り表示するという意味ではバックエンドとしては同じであり、フロントエンドの分割はまた別の世界の話になるからです。
Controller(コントローラー)
役割
リクエスを受けて・レスポンスを返却する役割です。Laravelでは最初からこの役割がはじめから存在します。
あらかじめ、リクエストのバリデーションや業務要件のバリデーションもここで行います。
ルーティングやフィルター(laravelではミドルウェア)など共通処理を通って、(今回のモジュール分割の中では)最初にリクエストが着弾します。
呼び出し元・先
Serviceを呼び出します
Service (サービス)
役割
Webサービス固有の業務ロジックやデータ操作処理を呼び出す(データ操作は直接しない)役割です。各モジュールの橋渡しをするような機能を提供します。
このサービスに処理を追加して、育てていくことにより他のモジュールがファットになるのを防いだり、使い回しのしやすい粒度のメソッドを増やして共通化を進めていくことになります(理想通りには行きませんが...)
呼び出し元・先
Controller → Service となり、コントローラや他のサービスからも呼ばれたりします。
Repository(リポジトリ)
役割
データ操作のSQLやO/Rマッパーを呼び出してのデータ操作処理の役割です。こいつが唯一DBからのデータ操作の役割を担います。なので、他のクラスは直接DB操作をしたり、するのは原則NGです。
DB処理は、各モジュールに飛び散る可能性が高く(エンジニアの横着やルールの認識不足などで)・そのデータの加工処理もセットでついて周りコードが汚れる原因になりやすいので注意が必要です。
呼び出し元・先
Controller → Service → Repository → DB(O/Rマッパー) となります。
Model(モデル)
役割
DBのテーブルに対応するオブジェクト定義です。この扱いが、言語やエンジニア同士で一番齟齬のある役割です。
今回は、Laravelの使い方と同様にテーブルに対応するオブジェクトの定義(エンティティと呼ばれたりもする)やリレーションの定義のみを記載するものとしています。DB操作処理やドメイン・業務ロジックは入れません(他のServiceやRepositoryがその役割)。
ですので、Modelは定義のみの記述となり、ファットになることは今回の分割方式だとありません。
呼び出し元・先
Repositoryから主に呼ばれます。Modelで定義している定数を参照する場合があるので、各クラスからも呼ばれたりします。
Presenter(プレゼンター)
役割
取得したデータを最終的に加工・出し分け等の役割です。このクラスは、唯一省略しても良いモジュールです。最終的に取得したデータを表示形式にあわせて形式やフォーマットを変換したりする処理をします。
よくある、View側で日付のフォーマットをするのかバックエンド渡す側で加工するのか問題は、コイツが処理(返却形式のフォーマット加工などをする)します。
省略した場合は、コントローラーがその役割をにないます。ただ、何もしなくてもコントローラーはファットになっていき、可読性がさがる傾向が多いので、あらかじめモジュールを分割していたほうが、結果コードがきれいになる場合が多いです
呼び出し元・先
Contlollerからよばれます
モジュール分割の処理の流れ
上記の分割方針の処理の流れを図にすると、下記流れです。
- リクエストをサーバーがうけます。WebサーバーやFWで色々前処理を行いルーティングに従って、該当のControllerにリクエストが渡される。
- Controllerは、業務に関連するバリデーションチェック行い、入力パラメータをServiceに渡します。
- Serviceは、入力内容をもとに必要な情報を構築します。この際にDBデータ操作がある場合は、直接行わず操作に必要な情報をRepositoryに渡します。他のServiceの処理を呼び出してもOKです。
- Repositoryは、DBへのクエリ操作のみを行います。ここに業務ロジックを入れるのは避けた方が、疎結合・汎用性が高くなるので良いほうが多いです。
- SQLやO/Rマッパーに従ってクエリを投げます。Repositoryを作成する際にIntefaceを挟んでおくと、DBを差し替える場合やDB以外のリソースを参照する際に可搬性が上がります。(そのような自体はほぼありませんが)
- DBから取得した値をそのまま渡します。O/Rマッパーを使っている場合は、Modelオブジェクトが渡されます。データの中身の取得や加工は、ここでは行いません。理由は4と同じです。
- Repositoryは、そのまま渡します。
- 値を受け取ったServiceで、Modelから値を取得し、ある程度までの加工を行います。なぜ、ある程度までかというと、完全なリクエスト返却形式をここで作成してしまうと、他のクラスや機能で使いまわしが難しくなるからです。例えば、ここでJson形式で返却した場合、他の機能などで配列や特定要素のみに加工したくなった場合、使い回しができないケースが多い(似たようなメソッドが増える)ので、「ある程度」「いい感じ」にしておくと汎用性が高くなります
- ここでControllerは、レスポンスに必要なデータは揃いました。が、そのままレスポンスデータの加工はここでせず、Presenterにデータを渡します。
- Presenterでは、レスポンスに必要なデータを受け取り加工を行います。主にフォーマットや返却形式に変換(json・xml・viewに渡すkey/value形式など)します。データが足りないからと言って、ServiceやRepositoryをここから呼ぶのはNGです。
- 最終返却形式に加工されたデータを受け取り、Controllerはレスポンスデータを作成し、データを返却します。
蓋を開けてしまえば、昔からよくある処理レイヤーごとに分けたモジュール分割にプレゼンターを追加したモジュール分割方式でした。
各名称が違うだけで、色々プロジェクトで見かける方式かもしれません。
はじまりの村へもう一度
この内容は、Webエンジニア初学者には難しい内容です。中級者程度でいくつかのプロジェクトを経験し、コードのつらみの部分を実感しているとより効いてくる内容かもしれません。
今はわからなくて、読み飛ばしても結構です。ただ、旅に出て力をつけたときまたこのはじまりの村に戻ってきて、再度読んでみてください。あの時わからなかった感覚や内容が、実体験をともなって理解でき、役に立つかもしれません。
旅の仲間
今後の記事でこの記事の用語や前提となる知識やそれに付随するプロジェクトアレコレを色々書いていき、良きコードが書けるような、頭の良くなった気になれる記事を提供していく予定です。おつかれさまでした。