Laravel大規模開発入門!MVC分離のFatModel問題に対する責任分離と依存管理、その設計と考え方について

ナイル株式会社メディアテクノロジー事業本部の工藤さんにMVC分離のFatModel問題に対する責任分離と依存管理、その設計と考え方について解説いただきました。

Laravel大規模開発入門!MVC分離のFatModel問題に対する責任分離と依存管理、その設計と考え方について

こんにちは、ナイル株式会社メディアテクノロジー事業本部で開発マネージャをしています工藤@ta99toです。

今回は大規模で複雑度の高い開発をMVCフレームワークベースで構築する際に僕が課題と捉えているポイントやその具体的な解決手法について解説させていただきたいと思います。

  • 「MVC以上の責任分離イメージがつかないよ!」
  • 「DDDとかクリーンとかオニオンとかあのへんの設計パターンの導入モチベーションが不明」
  • 「どうやっても最終的には複雑になって追加開発や修正開発が怖い状態になっちゃう」

↑このような悩みを持った方に対して本質的な課題の構造に対する理解が深まったり、その解決手法を提案するようなエントリーとなれば良いなと考えています。 上記に少しでも共感のあったwebエンジニアの方はぜひ最後まで目を通してみてくださいね。

また、エンジニアの方でなくてもなんとなくわかったような気になれる表現を目指しますので業務上よくエンジニアと関わるよという方もぜひ読んでみてください!

概要

システム開発はその規模が大きくなればなるほど検知しにくい技術的な課題を内部に累積しやすくなってしまいます。

そのようないわゆる「技術的な負債」を開発プロジェクトが抱えてしまう現象の主な要因の1つとして、自分たちの実装したプログラムの1つ1つに対して適切に責任や役割を与えたり分担させたりするような責任分離、依存管理のコントロールができていないということが挙げられます。

規模によって適切な責任分担、役割分担の粒度は変わるのにそれに対応する設計手段、実装手段を持たずにいると要件とスキルのミスマッチが発生し、そのギャップが様々な課題を引き起こす要因となってしまいます。

こうしたシステムの要件、規模の増大が引き起こす複雑さの解決に挑戦してきた先人たちの歩み、アウトプットが各種設計アーキテクチャであり、オブジェクト指向です。

近年のweb開発ではMVCフレームワークを利用した開発が一般的になっていて小さなアプリケーションであれば特に難しいことを考えなくても動く物が実装可能になっているのですが、中規模以上のものを流動的な要件に対応しやすい状態を保ち運用開発していくことを実現しようとすると先述したエンジニアリングスキルが欠けた状態では対応が難しい場合が往々にしてあります。

本稿ではそのようなシステム開発の複雑さを引き起こす課題の真因について考察し、解決の具体的な手法や設計アーキテクチャのさわりの部分を簡単に紹介したいと思います。

LaravelなどMVCフレームワーク開発の抱える課題

課題の話をする上で理解が必要なフレームワークやORM、MVC分離など用語の説明をはじめにざっくりとしてから内容に入っていきたいと思います。

フレームワーク、ORM概要

めぼしいweb開発のフレームワークには大体このORM(Object Relational Mapping)と呼ばれる仕組みが用意されています。

名前の通りデータベースの1テーブル1テーブルと対になるようクラス(Object)を用意し、フレームワークで実装済みの処理を継承すればすぐにデータベースに対して読み書きを行う処理を実装可能になるというものです。


// BlogPostというクラス名を内部でblog_postsという文字列に変換してテーブルを探しに行きます
class BlogPost extends Model
{
}
// ブログ投稿を全て取得します
$allBlogPosts = BlogPostModel::all();

上記のようにRDBでサポートされているデータベースであればSQLを一切記述せずにデータベースとのやりとりを処理できます。

こうしたフレームワークが用意してくれるアセットを活用することで開発工数を短縮できるというのがフレームワーク導入の1つの大きなモチベーションです。

データベースとのやり取りを行う処理が抽象化されており、プログラマはallという関数の奥で実際どんな処理が実行されているのか知らずとも実現したい処理を実装することができるようになりました。

こうした「開発コストを下げるような仕組み」と、「MVC分離のルール」を与えてくれるのがフレームワークです。

MVC分離とは

webサービスやモバイルアプリなどのUIが存在するアプリケーション開発において、

  • 見た目(View)
  • ユーザ入力の解釈、処理の実行(Controller)
  • ビジネスロジック(Model)

の3つに分けて開発することで保守性、運用性を担保しようとする設計方針の1種です。

ビジネスロジックというのは例えばTwitterのようなSNSのシステムで言うなら

  • 「ツイートはリツイートできる」
  • 「ツイート内容にメンション付いてたら該当ユーザに通知飛ばす」

などのシステムの中心に存在するデータ構造とその振る舞いに関する実装のことです。

MVCフレームワークの功罪

という訳で、肝心なのはモジュール間の責任分離や依存管理をうまくやりくりする事であって、MVCとはそのための手段、概念の1つです。

ところがMVCフレームワーク繁栄の結果としてこの辺の勘所を抑えないままとりあえずMVCで分けるということだけが広く浸透しました。

結果としてFatControllerやFatModelと呼ばれる、ControllerやModelの1クラスに数百行もの処理を書いてしまう実装が量産され一時期話題になったこともあります。

  • データ構造や処理フローをオブジェクトで表現するというオブジェクト指向プログラミングの基礎
  • ソースコードの品質を維持して実装を拡張していくにはどうすればいいか

このような基礎が無いままMVCフレームワークというツールだけが広まっていったことで、Modelにはデータベースとのやり取りに関する情報が溢れて本質的なデータ構造やビジネスロジックが霞み、Controlerには漏れ出したビジネスロジックや分岐がだらだらと書かれてシェルスクリプトかな状態。

こうした残念な品質を負債として抱えたプロダクトがすごく増えました。

一方でこうした「フレームワーク」と言う形で提供された開発ワークフローはweb開発のハードルを大幅に引き下げ、実に多くのプロダクトの誕生に世界中で貢献しています。

そういう意味では数多あるOSSプロダクトの中でも「フレームワーク」と言うカテゴリが実世界に及ぼした益は非常に大きなものがあります。

課題の正体

MVCフレームワークを扱えるwebエンジニアはたくさんいるんですが、モジュール間の適切な責任分離、依存管理、情報設計を行い事業のスケールに合わせてシステムを安心、安全に拡張、拡大していける仕組みづくり、旗振りをできるエンジニアがあまりいないのです。

そんな状態で進める開発プロジェクトは以下のような課題を抱えます。

  • 再利用性が低く開発が進んでも進んでも楽にならない。コードの増加は複雑度の増加に比例しどんどん開発スピードが鈍化する。
  • 依存管理の質が低くあっちを直せばこっちが壊れる。逆に1箇所直せば済むような修正のはずがあちこちいじる必要がある。
  • 情報設計の質が低くデータベースの正規化がうまくいってない。不自然なUI、不自然なデータ構造。
  • 理想形のイメージがないのでコードレビューや設計レビューで突っ込むことなくてレビュー体制が形骸化してる。
  • ソースコードの見通しが悪くキャッチアップするのに時間がかかる。

理想は以下のような状態をつくることです。

  • 再利用性が高く開発が進むほど安全で便利なモジュールが増えて新規開発や修正が楽になり開発スピードが上がる。
  • 依存管理の質が高く改修の影響範囲が明確で1箇所直せば必要な箇所は全て直る。
  • 情報設計の質が高くデータベースは適切に正規化、必要なところは冗長化されている。自然なUI、自然なデータ構造。
  • 理想形のイメージがありそれに沿わない設計や実装を拒否、改善する仕組みとしてレビューがワークしている。
  • ソースコードの見通しが良くキャッチアップに時間がかからない。

こうした理想状態の実現を妨げる課題は何でしょうか。

それはどちらかと言えば

「プログラミングの難しさ」や「開発プロジェクトマネジメントの難しさ」

と言うよりは

「自分ではない何かの集合(人ないしプログラム)に適切に責任や役割を与えて代わりに仕事をしてもらうことの難しさ」

だと僕は考えています。

システムを組織として見た時、従業員としてのソースコードを制御する仕組み

会社組織における未熟と成熟

会社組織というのは良くできた仕組みで、適切に責任分離、目標設定、役割分担された組織というのは細かい指示や管理が無くても目標に対して個人個人が有機的に動き協調しあって成果をあげます。

反対にそうでない組織、責任範囲が曖昧だったり役割が重複していたりなど設計に問題のある組織においては途端に成果をあげるどころかトラブルばかり、組織や人に起因する問題解決に終始してしまい事業成長どころではありません。

更にはこうした人や組織構造に起因する課題は見えないところでゆっくりと進むため、目に見えるほど不具合を蓄積してそれを検知した時点で課題のサイズは既に大きく複雑に膨らんでいて解決するのに時間を要します。

システム開発における未熟と成熟

システム開発においても同様にうまく責任分離、依存管理がされていない実装同士がうまく協調することは難しく、複数人で開発しているなら尚更、そのような状況でプログラマAとプログラマBの実装がうまく協調して成果をあげると言うのは難しいわけです。

こうした不協和音を検知できず放置した結果が故障率や開発スピードの低下といったビジネスマネージャらの目にも見えるほどの影響を及ぼし出す頃にはもう既に手遅れ、システムという名の組織は既に壊死しており部分最適でなんとかなる状況ではなくなっています。

反対に、綺麗に設計されたシステムの上でなら実装と実装は開発者も驚くほど美しく繋がり、連携し、成果をあげるものです。

結果の差異を生むもの

あなたが綺麗に設計された組織にいる場合、あなたに任された責任や権限、目標は明確であり、ある日何かの業務に対応しようとした時にその業務が依存する部署、連携の必要なメンバーは明らかであり、迷って動けないと言うことはないでしょう。

例えば「新しいツールを導入したい」と思った時、それは

  • 稟議の作成
  • 上司の承認
  • 法務のリーガルチェック

の3つに依存することが明確で、その通りにすればやりたいことが達成できると言うことが明らかです。

  • あなたはプロジェクト内で任された責任を全うするために稟議を作成、提案する権限を持っている。
  • あなたの上司は予算管理とプロジェクト目標達成の責任を全うするために提案された内容を承認したり差し戻す権限を持っている。
  • 法務は組織にリスクのあるツールを導入させないという責任を果たすためリーガルチェックを実行する権限を持っている。

こうした会社組織内におけるチームとチーム、人と人の自立的で安全な連携を可能にしているのが

  • チームやロールの責任範囲と権限が明確であること
  • 業務と業務の依存関係が明確であること

なのです。

一方こうした設計の破綻した組織ではどうでしょう。以下のようなことが起きてしまう可能性があります。

  • 稟議は作成されたりされなかったり、誰にチェックしてもらえばいいかわからない
  • 管理職のあずかり知らないところで受発注が行われている
  • リリースしたサービスが実は法律に違反していた

どうでしょうか。こんなことは例に挙げたほど極端ではないにしてもあちこちで実際に起きていることです。

こうした責任範囲と権限の暴走、依存関係の不明確さが不都合を起こすのはシステムにおいてもまた同じで、そうしたシステムでは

  • メインルーチンを読んでも何がしたいのかわからない(ビジネスロジックの漏洩、宣言的でない手続き的な実装)
  • 入力と出力が不明確で再利用が怖い(依存関係の不明確)
  • どこからどれくらい呼ばれてるんだか分からないグローバルな変数や関数(責任範囲の不明確、依存関係管理の放棄)

↑こういった状況が発生しています。

こうした現象を避けるために、

  • 矛盾や無駄のない整合性の取れた情報設計
  • 1つのまとまりが1つの目的に集中できるような責任分離
  • オブジェクト間の関係を明確に説明する依存管理

が必要なのです。

課題考察まとめ

とりあえず何度も書いたこと、

  • 責任分離
  • 依存管理
  • 情報設計

がシステム開発には(一般的な会社組織における組織設計にて重要とされるのと同程度かそれ以上には)重要なんだ、と言う主旨についてはなんとなくご理解いただけたんじゃないでしょうか。

それでは具体的にどう解決するのかと言うのを次のセクションからコードも添えながら見ていきたいと思います。

Laravelの例に見るFatModel/FatController問題の解決

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く