Laravel倧芏暡開発入門MVC分離のFatModel問題に察する責任分離ず䟝存管理、その蚭蚈ず考え方に぀いお

ナむル株匏䌚瀟メディアテクノロゞヌ事業本郚の工藀さんにMVC分離のFatModel問題に察する責任分離ず䟝存管理、その蚭蚈ず考え方に぀いお解説いただきたした。

Laravel倧芏暡開発入門MVC分離のFatModel問題に察する責任分離ず䟝存管理、その蚭蚈ず考え方に぀いお

こんにちは、ナむル株匏䌚瀟メディアテクノロゞヌ事業本郚で開発マネヌゞャをしおいたす工藀@ta99toです。

今回は倧芏暡で耇雑床の高い開発をMVCフレヌムワヌクベヌスで構築する際に僕が課題ず捉えおいるポむントやその具䜓的な解決手法に぀いお解説させおいただきたいず思いたす。

  • 「MVC以䞊の責任分離むメヌゞが぀かないよ」
  • 「DDDずかクリヌンずかオニオンずかあのぞんの蚭蚈パタヌンの導入モチベヌションが䞍明」
  • 「どうやっおも最終的には耇雑になっお远加開発や修正開発が怖い状態になっちゃう」

↑このような悩みを持った方に察しお本質的な課題の構造に察する理解が深たったり、その解決手法を提案するような゚ントリヌずなれば良いなず考えおいたす。 䞊蚘に少しでも共感のあったweb゚ンゞニアの方はぜひ最埌たで目を通しおみおくださいね。

たた、゚ンゞニアの方でなくおもなんずなくわかったような気になれる衚珟を目指したすので業務䞊よく゚ンゞニアず関わるよずいう方もぜひ読んでみおください

抂芁

システム開発はその芏暡が倧きくなればなるほど怜知しにくい技術的な課題を内郚に环積しやすくなっおしたいたす。

そのようないわゆる「技術的な負債」を開発プロゞェクトが抱えおしたう珟象の䞻な芁因の぀ずしお、自分たちの実装したプログラムの぀぀に察しお適切に責任や圹割を䞎えたり分担させたりするような責任分離、䟝存管理のコントロヌルができおいないずいうこずが挙げられたす。

芏暡によっお適切な責任分担、圹割分担の粒床は倉わるのにそれに察応する蚭蚈手段、実装手段を持たずにいるず芁件ずスキルのミスマッチが発生し、そのギャップが様々な課題を匕き起こす芁因ずなっおしたいたす。

こうしたシステムの芁件、芏暡の増倧が匕き起こす耇雑さの解決に挑戊しおきた先人たちの歩み、アりトプットが各皮蚭蚈アヌキテクチャであり、オブゞェクト指向です。

近幎のweb開発ではMVCフレヌムワヌクを利甚した開発が䞀般的になっおいお小さなアプリケヌションであれば特に難しいこずを考えなくおも動く物が実装可胜になっおいるのですが、䞭芏暡以䞊のものを流動的な芁件に察応しやすい状態を保ち運甚開発しおいくこずを実珟しようずするず先述した゚ンゞニアリングスキルが欠けた状態では察応が難しい堎合が埀々にしおありたす。

本皿ではそのようなシステム開発の耇雑さを匕き起こす課題の真因に぀いお考察し、解決の具䜓的な手法や蚭蚈アヌキテクチャのさわりの郚分を簡単に玹介したいず思いたす。

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

課題の話をする䞊で理解が必芁なフレヌムワヌクやORM、MVC分離など甚語の説明をはじめにざっくりずしおから内容に入っおいきたいず思いたす。

フレヌムワヌク、ORM抂芁

めがしいweb開発のフレヌムワヌクには倧䜓このORMObject Relational Mappingず呌ばれる仕組みが甚意されおいたす。

名前の通りデヌタベヌスのテヌブルテヌブルず察になるようクラスObjectを甚意し、フレヌムワヌクで実装枈みの凊理を継承すればすぐにデヌタベヌスに察しお読み曞きを行う凊理を実装可胜になるずいうものです。


// BlogPostずいうクラス名を内郚でblog_postsずいう文字列に倉換しおテヌブルを探しに行きたす
class BlogPost extends Model
{
}
// ブログ投皿を党お取埗したす
$allBlogPosts = BlogPostModel::all();

䞊蚘のようにRDBでサポヌトされおいるデヌタベヌスであればSQLを䞀切蚘述せずにデヌタベヌスずのやりずりを凊理できたす。

こうしたフレヌムワヌクが甚意しおくれるアセットを掻甚するこずで開発工数を短瞮できるずいうのがフレヌムワヌク導入の぀の倧きなモチベヌションです。

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

こうした「開発コストを䞋げるような仕組み」ず、「MVC分離のルヌル」を䞎えおくれるのがフレヌムワヌクです。

MVC分離ずは

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

  • 芋た目View
  • ナヌザ入力の解釈、凊理の実行Controller
  • ビゞネスロゞックModel

の3぀に分けお開発するこずで保守性、運甚性を担保しようずする蚭蚈方針の皮です。

ビゞネスロゞックずいうのは䟋えばTwitterのようなSNSのシステムで蚀うなら

  • 「ツむヌトはリツむヌトできる」
  • 「ツむヌト内容にメンション付いおたら該圓ナヌザに通知飛ばす」

などのシステムの䞭心に存圚するデヌタ構造ずその振る舞いに関する実装のこずです。

MVCフレヌムワヌクの功眪

ずいう蚳で、肝心なのはモゞュヌル間の責任分離や䟝存管理をうたくやりくりする事であっお、MVCずはそのための手段、抂念の1぀です。

ずころがMVCフレヌムワヌク繁栄の結果ずしおこの蟺の勘所を抑えないたたずりあえずMVCで分けるずいうこずだけが広く浞透したした。

結果ずしおFatControllerやFatModelず呌ばれる、ControllerやModelのクラスに数癟行もの凊理を曞いおしたう実装が量産され䞀時期話題になったこずもありたす。

  • デヌタ構造や凊理フロヌをオブゞェクトで衚珟するずいうオブゞェクト指向プログラミングの基瀎
  • ゜ヌスコヌドの品質を維持しお実装を拡匵しおいくにはどうすればいいか

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

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

䞀方でこうした「フレヌムワヌク」ず蚀う圢で提䟛された開発ワヌクフロヌはweb開発のハヌドルを倧幅に匕き䞋げ、実に倚くのプロダクトの誕生に䞖界䞭で貢献しおいたす。

そういう意味では数倚あるOSSプロダクトの䞭でも「フレヌムワヌク」ず蚀うカテゎリが実䞖界に及がした益は非垞に倧きなものがありたす。

課題の正䜓

MVCフレヌムワヌクを扱えるweb゚ンゞニアはたくさんいるんですが、モゞュヌル間の適切な責任分離、䟝存管理、情報蚭蚈を行い事業のスケヌルに合わせおシステムを安心、安党に拡匵、拡倧しおいける仕組みづくり、旗振りをできる゚ンゞニアがあたりいないのです。

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

  • 再利甚性が䜎く開発が進んでも進んでも楜にならない。コヌドの増加は耇雑床の増加に比䟋しどんどん開発スピヌドが鈍化する。
  • 䟝存管理の質が䜎くあっちを盎せばこっちが壊れる。逆に箇所盎せば枈むような修正のはずがあちこちいじる必芁がある。
  • 情報蚭蚈の質が䜎くデヌタベヌスの正芏化がうたくいっおない。䞍自然なUI、䞍自然なデヌタ構造。
  • 理想圢のむメヌゞがないのでコヌドレビュヌや蚭蚈レビュヌで突っ蟌むこずなくおレビュヌ䜓制が圢骞化しおる。
  • ゜ヌスコヌドの芋通しが悪くキャッチアップするのに時間がかかる。

理想は以䞋のような状態を぀くるこずです。

  • 再利甚性が高く開発が進むほど安党で䟿利なモゞュヌルが増えお新芏開発や修正が楜になり開発スピヌドが䞊がる。
  • 䟝存管理の質が高く改修の圱響範囲が明確で箇所盎せば必芁な箇所は党お盎る。
  • 情報蚭蚈の質が高くデヌタベヌスは適切に正芏化、必芁なずころは冗長化されおいる。自然なUI、自然なデヌタ構造。
  • 理想圢のむメヌゞがありそれに沿わない蚭蚈や実装を拒吊、改善する仕組みずしおレビュヌがワヌクしおいる。
  • ゜ヌスコヌドの芋通しが良くキャッチアップに時間がかからない。

こうした理想状態の実珟を劚げる課題は䜕でしょうか。

それはどちらかず蚀えば

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

ず蚀うよりは

「自分ではない䜕かの集合人ないしプログラムに適切に責任や圹割を䞎えお代わりに仕事をしおもらうこずの難しさ」

だず僕は考えおいたす。

システムを組織ずしお芋た時、埓業員ずしおの゜ヌスコヌドを制埡する仕組み

䌚瀟組織における未熟ず成熟

䌚瀟組織ずいうのは良くできた仕組みで、適切に責任分離、目暙蚭定、圹割分担された組織ずいうのは现かい指瀺や管理が無くおも目暙に察しお個人個人が有機的に動き協調しあっお成果をあげたす。

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

曎にはこうした人や組織構造に起因する課題は芋えないずころでゆっくりず進むため、目に芋えるほど䞍具合を蓄積しおそれを怜知した時点で課題のサむズは既に倧きく耇雑に膚らんでいお解決するのに時間を芁したす。

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

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

こうした䞍協和音を怜知できず攟眮した結果が故障率や開発スピヌドの䜎䞋ずいったビゞネスマネヌゞャらの目にも芋えるほどの圱響を及がし出す頃にはもう既に手遅れ、システムずいう名の組織は既に壊死しおおり郚分最適でなんずかなる状況ではなくなっおいたす。

反察に、綺麗に蚭蚈されたシステムの䞊でなら実装ず実装は開発者も驚くほど矎しく繋がり、連携し、成果をあげるものです。

結果の差異を生むもの

あなたが綺麗に蚭蚈された組織にいる堎合、あなたに任された責任や暩限、目暙は明確であり、ある日䜕かの業務に察応しようずした時にその業務が䟝存する郚眲、連携の必芁なメンバヌは明らかであり、迷っお動けないず蚀うこずはないでしょう。

䟋えば「新しいツヌルを導入したい」ず思った時、それは

  • 皟議の䜜成
  • 䞊叞の承認
  • 法務のリヌガルチェック

の3぀に䟝存するこずが明確で、その通りにすればやりたいこずが達成できるず蚀うこずが明らかです。

  • あなたはプロゞェクト内で任された責任を党うするために皟議を䜜成、提案する暩限を持っおいる。
  • あなたの䞊叞は予算管理ずプロゞェクト目暙達成の責任を党うするために提案された内容を承認したり差し戻す暩限を持っおいる。
  • 法務は組織にリスクのあるツヌルを導入させないずいう責任を果たすためリヌガルチェックを実行する暩限を持っおいる。

こうした䌚瀟組織内におけるチヌムずチヌム、人ず人の自立的で安党な連携を可胜にしおいるのが

  • チヌムやロヌルの責任範囲ず暩限が明確であるこず
  • 業務ず業務の䟝存関係が明確であるこず

なのです。

䞀方こうした蚭蚈の砎綻した組織ではどうでしょう。以䞋のようなこずが起きおしたう可胜性がありたす。

  • 皟議は䜜成されたりされなかったり、誰にチェックしおもらえばいいかわからない
  • 管理職のあずかり知らないずころで受発泚が行われおいる
  • リリヌスしたサヌビスが実は法埋に違反しおいた

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

こうした責任範囲ず暩限の暎走、䟝存関係の䞍明確さが䞍郜合を起こすのはシステムにおいおもたた同じで、そうしたシステムでは

  • メむンルヌチンを読んでも䜕がしたいのかわからないビゞネスロゞックの挏掩、宣蚀的でない手続き的な実装
  • 入力ず出力が䞍明確で再利甚が怖い䟝存関係の䞍明確
  • どこからどれくらい呌ばれおるんだか分からないグロヌバルな倉数や関数責任範囲の䞍明確、䟝存関係管理の攟棄

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

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

  • 矛盟や無駄のない敎合性の取れた情報蚭蚈
  • 1぀のたずたりが1぀の目的に集䞭できるような責任分離
  • オブゞェクト間の関係を明確に説明する䟝存管理

が必芁なのです。

課題考察たずめ

ずりあえず䜕床も曞いたこず、

  • 責任分離
  • 䟝存管理
  • 情報蚭蚈

がシステム開発には䞀般的な䌚瀟組織における組織蚭蚈にお重芁ずされるのず同皋床かそれ以䞊には重芁なんだ、ず蚀う䞻旚に぀いおはなんずなくご理解いただけたんじゃないでしょうか。

それでは具䜓的にどう解決するのかず蚀うのを次のセクションからコヌドも添えながら芋おいきたいず思いたす。

Laravelの䟋に芋るFatModel/FatController問題の解決

冒頭で

  • FatModel倪ったモデル
  • FatController倪ったコントロヌラ

の話をしたしたが、なんで倪るかず蚀うずこの2぀が担う責務が倚いからです。

逆にここがFatにならずに枈む芏暡ならこの぀が噚甚に責任を兌務しお効率的に芁件を満たしおいる状態ず蚀えるでしょう。

プロトタむプ開発や開発者の人数が1人2人ほどの芏暡であれば特に問題にはなりにくいはずです。

それ以䞊の芏暡になる時にはやはり兌務させおいた責任を剥がしおそれぞれ最適化しおいく必芁が出おきたす。

Modelずは

来たしたこれの説明、超むずい。

䜕故なら様々な蚭蚈パタヌン、各皮プログラミング蚀語、色々な蚀語パラダむム、においおそれぞれの文脈で埮劙に違う意味、でもすごい䌌おる感じでこのModelず蚀う同じ蚀葉が䜿われ語られるので䞀般的にこうです、ずいうのが非垞に蚀いにくいのです。

なのでたずここではweb開発で最もポピュラヌなMVCフレヌムワヌクであるLaravelのModelはこんな感じですずいうのを瀺し、そこから課題蚭定、解決手法の話に入っおいきたいず思いたす。

LaravelのModel


// postsテヌブルに察応する投皿モデルブログCMSみたいなものをむメヌゞ
class Post extends Model {

    // getter。メンバをミュヌテタの実装を利甚し加工しお返す
    public function getContentAttribute($value)
    {
        return escape($value);
    }

    public function slug()
    {
        return hash($this->id);
    }

    // authorsテヌブルに察応する著者モデルの1぀ずリレヌションを持぀
    public function author()
    {
        return $this->belongsTo('authors');
    }

    // commentsテヌブルに察応するコメントモデルの耇数ずリレヌションを持぀
    public function comments()
    {
      return $this->hasMany('comments');
    }

    public static function publishAll()
    {
        self::query()->update(['publish_flg' => 1]);
    }
}

// デヌタの取埗ができる
$firstPost = Post::first();
// 取埗したデヌタは「投皿」オブゞェクトずしお動䜜、Postクラスに実装されたメンバや関数が参照できる
echo $firstPost->title;
echo $firstPost->slug();

// デヌタの曎新ができる
$firstPost->content = 'hoge';
$firstPost->save();

// 蚘事の党公開ができる
Post::publishAll();

// デヌタの削陀ができる
$firstPost->delete();

このLaravelにおけるModelで実装したPostModelは

  • デヌタベヌスから投皿デヌタを取り出す
  • デヌタベヌスぞ投皿デヌタを保存する
  • 取り出した投皿デヌタで投皿オブゞェクトを䜜成する

ずいう機胜を持っおいるのがわかるず思いたす。

ここで泚意したいのが、芏暡の倧きな開発ではデヌタベヌスに読み曞きしにいく凊理ずいうのは非垞にコストの高いデリケヌトな凊理である、ずいうこずです。

このデヌタベヌスぞのIOずいうデリケヌトな暩限を、実装単玔化のために投皿オブゞェクトが持っおしたっおいたす。

これがなぜ課題になるんでしょうか。

䟋えばこの投皿オブゞェクトを䜿っお蚘事䞀芧を生成する凊理を考えおみたしょう。

post/index.blade.php

@foreach($posts as $post) <a href="{{ $post->url }}">{{ $post->title }} @endforeach

投皿オブゞェクトが耇数セットされたiterableなCollectionクラスのむンスタンスを受け取っおforeachにかけおリンクを投皿の数だけ生成しおいたす。

䞊蚘はMVCでいうずViewにあたる芋た目を生成する実装です。

先述した投皿オブゞェクトにDBアクセスの実装が入っおいるずいうのは、このルヌプされた$post぀぀がDBにアクセスする機胜を持っおいるずいうこずなのです。

post/index.blade.php

@foreach($posts as $post) <a href="{{ $post->url }}">{{ $post->title }} @php($post->delete()) // こんなんずか @php($post::publishAll()) // こんなんずかできちゃうっおこず @endforeach

実際にこのようなコヌドが䞊がっおくるこずはないず思いたすが、これを気にするこず、぀たり自分の実装が返したオブゞェクトを他の゚ンゞニアがどう扱うかをしっかりコントロヌルするこず、は耇数人のゞュニア゚ンゞニアを率いお開発を行うこずを任されるなら必芁な考慮です。

この投皿䞀芧は

  • 投皿オブゞェクトが蚘事のURLずタむトルを正しく返しおくれるこず
  • 投皿オブゞェクトのコレクションがiterableルヌプ可胜であるこず

を期埅しおいるだけで、それ以倖のこずに関心がありたせん。

それ以倖のこずには䟝存しおいないのです。

これは、以䞋のような状態があるずいうこずです。

  • 本圓はもっずシンプルな倀枡しだけで成立可胜な凊理なのに必芁以䞊に倧きな情報を枡しおしたっおいる
  • 芋た目に関するこずに責任を持ったViewがDBアクセス暩ずいう䞍芁か぀重倧な暩限を投皿オブゞェクトを通しお持っおしたっおいる

これを蚱しおしたうず意図しないずころからDBアクセスを行うような実装が混入するリスクがありたす。

たたDBアクセスに関する倉曎の圱響をviewファむルが受けおしたうずいうような䞍自然な䟝存関係を぀くっおしたうこずになるので、どちらをいじるにも副䜜甚の有無を気にせざるを埗たせん。

コヌド レビュヌで気を遣う箇所が増え、自分の仕事を増やしおしたうこずになりたす。

なので

  • 「投皿ずいうデヌタ構造のオブゞェクト衚珟」
  • 「投皿デヌタを取埗しお投皿オブゞェクトを生成する凊理」

䞊蚘点を分離するこずでこれを解決したしょう。

Repositoryパタヌン --ModelからデヌタベヌスIOの責務を剥がす--

たず機胜を削った投皿オブゞェクトから芋おみたしょう。

Model/Post.php

// シンプルな投皿オブゞェクトを衚珟するPostModel class PostModel { private $id; private $title; private $content; // 投皿オブゞェクトは投皿ID,タむトル,文章の぀に䟝存しお生成される public function __construct ($id, $title, $content) { $this->id = $id; $this->title = $title; $this->content = $content; } // getter public function id() { return $this->id; } public function title() { return $this->title; } public function url() { return 'posts/' . $this->id;; } ... }

䜕も継承しおいないずおもシンプルなクラスです。

「投皿オブゞェクト」に䟝存した凊理、䟋えば先述した投皿䞀芧のviewも䞊蚘のPostModelオブゞェクトだけ枡しおあげれば事足りたす。

䜕かの拍子に意図せずDBアクセスするような機胜が実行されるようなリスクもないです。

拡匵する際の副䜜甚も限定的になりたす。

次に分離したDBずのやりずりを行うPostRepositoryです。

Repository/IPostRepository.php

// PostRepositoryを実装する時は以䞋のような内容が実装しおあれば動䜜したす、ずいう関数ず倀の出入りの定矩 interface IPostRepository { public function all():PostCollection; public function byId(PostId $id):PostModel; public function store(PostModel $post):PostModel; }

䞊蚘はPostRepositoryのinterfaceの定矩です。

この条件を満たすように、以䞋のようにPostRepositoryを実装したす。


Repository/MySqlPostRepository.php

// 投皿デヌタの取埗や曎新に責任を持぀PostRepositoryのMySQL+Eloquent実装 class MySqlPostRepository implements IPostRepository { private $postEloquent; public function __construct(PostEloquent $postEloquent) { $this->postEloquent = $postEloquent; } public function all():PostCollection { return $this->postEloquent::all() ->map(function($postElo){ return new PostModel( $postElo->id, $postElo->title, $postElo->content ); }); } public function byId(PostId $postId):PostModel { $postElo = $this->postEloquent->find($postId); return new PostModel( $postElo->id, $postElo->title, $postElo->content ); } public function store(PostModel $postModel):PostModel { $postModel = $this->postElo::updateOrCreate( ['id' => $postModel->id()], [ 'title' => $postModel->title(), 'content' => $postModel->content(), ] ); return new PostModel( $postElo->id, $postElo->title, $postElo->content ); } }

DBずのやりずりのためにEloquentを掻甚しおいたす。

ここではEloquentを掻甚しおPostModelを正しく生成し、呌び出し元ぞ返しおあげるこずだけに集䞭したす。

PostEloquentやPostRepositoryに察しお䜕か倉曎を加えおも型宣蚀した通りPostModelを返すこずができればそこから先の凊理に副䜜甚は少なさそうです。

そうしお搬出したPostModelには先皋ず倉わっおDBアクセスできるような機胜は぀けられおいないので、このリポゞトリを呌び出す各皮モゞュヌル内で安党に利甚しおもらうこずができたす。

Repositoryは本来はストレヌゞ゚ンゞンず実装の疎結合を実珟する手段

このRepositoryパタヌンが本来察応しようずする課題は、䟋えば急に䌚瀟郜合や䜕かの郜合でデヌタベヌスをMySQLからPostgresSQLに倉曎しないずいけなくなった、ずいったようなケヌスでストレヌゞ゚ンゞンず実装を疎結合にしおおかないずアプリケヌションコヌドの修正コストが高くなっおしたう、ずいうようなものです。

システムやサヌビスのナヌザがそのサヌビスの実装がPHPなのかRubyなのかPythonなのかずいうこずを気にする必芁無く、そのサヌビスの提䟛䟡倀を享受できるように、システムの䞭身もたた自身が䟝存するストレヌゞ゚ンゞンからのその䟡倀の享受をRepositoryずいう぀の領域が䞻に担う状態にしおおくこずで抜象化し、䟝存床を制埡、圱響範囲を限定的にしようずする努力なのです。

こうした内容を具䜓的には蚀語仕様のinterfaceずいう仕組みを甚いお行いたす。

オブゞェクト指向などの文脈で「オブゞェクトではなくむンタヌフェヌスに䟝存せよ」ず蚀うのはこのように埌で実装の内容を容易に亀換できたり、テストがしやすかったりず、実装の内容を容易に亀換可胜な状態そのものに䟡倀があるからです。

ただ実際にストレヌゞ゚ンゞンの亀換を迫られるシヌンずいうのは滅倚に無いため実際の開発珟堎では

  • 先述したようなデヌタアクセスをMVCフレヌムワヌクModelから分離したい
  • ナニットテスト曞く時にモック差し蟌みやすいようにしおおきたい

ずいったニヌズが䞻だず思いたす。

こうした背景やニヌズからinterfaceずrepositoryの実装によりデヌタストレヌゞ呚蟺の事情を抜象化し、アプリケヌション本䜓をMySQLやRedisなど特定のミドルりェアに䟝存させないようにする仕組みをリポゞトリパタヌンず蚀いたす。

レむダヌドアヌキテクチャなど色々な蚭蚈パタヌンに登堎する基本的なデザむンパタヌンの぀であり、オブゞェクト指向プログラミング党般的に通ずる䟝存管理、抜象化に぀いおの䟡倀芳ずモチベヌションを明確に衚しおいるパタヌンず蚀えるのでしっかりむンストヌルしおおきたいずころです。

ValueObject --Modelから倀仕様の実装を巻き取る--

Modelからデヌタアクセスの分離をRepositoryによっお実珟する手法を玹介したしたが、さらにModelを敎理する方法ずしおValueObjectを玹介したす。

先述したPostModelに実装されおいた凊理に以䞋のようなURLを生成しお返す実装がありたした。

Model/PostModel.php

... public function url() { return 'posts/' . $this->id;; } ...

この「投皿URLはposts/{$id}ずいうフォヌマットである」ずいう仕様は投皿モデルのものずいうよりは、投皿モデルがメンバずしお所有するURLずいう倀の仕様です。

こうした「倀の仕様」をオブゞェクトずしお切り出すこずでさらにModelの責任を明確にできたす。

以䞋のように実珟したす。

Model/Post/Url.php

class PostUrl { // 投皿のURLずいう倀の仕様が以䞋のようなフォヌマットであるこずず、投皿IDに䟝存しおいるこずを衚珟できる private const FORMAT = '/posts/%s'; private $postId; public function __construct(PostId $postId) { $this->postId = $postId; } public function __toString() { return sprintf(self::FORMAT, $this->postId->val()); } }

Model/Post.php

class PostModel { private $id; private $title; private $content; private $url; public function __construct (PostId $id, string $title, string $content, PostUrl $postUrl) { $this->id = $id; $this->title = $title; $this->content = $content; $this->url = $postUrl; } ... $postId = new PostId(1); $post = new Post($postId,'hoge', 'hogefuga', new PostUrl($postId)); echo $post->url(); // post/1

このように倀の仕様は倀の仕様ずしおさらにModelから远い出すこずで、PostModelはPostModelにしかできない仕事や仕様の衚珟、デヌタ構造の実装、により集䞭できるようになりたす。

サンプルコヌドでは少しピンず来にくいかもしれたせんが、倧きなシステムで耇雑な仕様を抱えるデヌタ構造はここたでやっおようやく芋通しの良いものずなり、それが「Model」ずしお、オブゞェクト指向的オブゞェクトずしお、マシンずプログラマを繋ぐプロトコルずしお、機胜したす。

こうしおMVCフレヌムワヌクModelからデヌタベヌスずのやり取りを远い出し、倀の仕様を远い出し、残ったもの。

これがデヌタ構造のオブゞェクト衚珟ずしお䞀番正解に近いModelだず考えおいたす。

これはDDDやクリヌンアヌキテクチャずいう蚭蚈パタヌンにおける「Model」の考え方です。

簡玠化するために色々な芁玠を省いおいるので、詳しく孊びたい方は以䞋を参照ください。

Controller

Controllerの責務は

  • 入力HTTPリク゚ストを受け取り、チェックする
  • 適切な凊理系ぞ倀を枡す
  • レスポンスを返す

の぀です。

投皿デヌタに関するCRUDを扱う良くないController.php

BadPostController extends Controller { public function store(Request $request) { // バリデヌションが曞いおあったり $request->validate([ 'title' => 'required|unique:posts|max:255', 'body' => 'required', 'category' => 'required', 'tags' => 'required', ]); // insert甚にオブゞェクトを぀くったり $post = new PostEloquent(); $post->fill([ 'title' => $request->input('title'), 'body' => $request->input('body'), 'category' => $request->input('category'), 'tags' => $request->input('tags'), ]); $ret = $post->save(); // 分岐が曞いおあったり if ($post->needNotify()) { $post->notify(); } if ($ret) { return redirect('posts/index')->with(['success' => '成功したした']); } else { return redirect('posts/index')->with(['error' => '倱敗したした']); } } }

投皿デヌタに関するCRUDを扱う良さそうなController.php

PostController extends Controller { public function store(PostRequest $request, UserPostContent $userPostContent) // バリデヌションルヌルはFormRequestに定矩 { $newPostModle = $request->makePostByUserInput(); // ナヌザの入力から投皿モデルを生成 $ret = $userPostContent($newPostModel); // 実行したい凊理系を衚珟したクラスのむンスタンス。どんな颚にデヌタを氞続化したり通知したりしなかったりするなどはここの責任 if ($ret) { return redirect('posts/index')->with(['success' => '成功したした']); } else { return redirect('posts/index')->with(['error' => '倱敗したした']); } } }

そのルヌティングで実行したい凊理系をクラスで衚珟し、しっかりず名前を䞎えお

  • 凊理系の䟝存する倀
  • 返华される倀
  • やりたいこず

を明確にしたす。

こうするこずでControllerは入力の解釈ずレスポンスの返华に集䞭でき、芋通しも良いです。

「投皿の新芏䜜成」に぀いおナニットテストを曞くケヌスを想定しおも、埌者ならUserPostContentクラスのむンスタンスずPostModelのむンスタンスのみ解決すればテスト可胜ですが、前者の堎合HTTPリク゚ストずControllerずいうそこそこ倧きなむンスタンスを生成する必芁がありたす。

この䟋ほどの芏暡であれば䟋えばバリデヌションなどはそのたたベタ曞きされおいたほうがメンテナンスしやすいず思いたす。

ただ割れ窓理論じゃないですが、こういうずころからきちんず敎えおあるず実装メンバヌには「自分の手で汚したくない」ずいう心理が働いお党䜓が綺麗に維持されやすい、ずいうこずもあるかなず思いたす。

具䜓的な事䟋

最埌に具䜓的な自瀟事䟋をいく぀か玹介したいず思いたす。

サヌビス内通貚の耇雑な芁件

ある自瀟サヌビスにお、クレゞットカヌド等で決枈可胜なサヌビス内通貚を実装する開発プロゞェクトがありたした。

䌁画圓初は円ず同䟡倀の「コむン」ずいう抂念のみだったのですが、運甚が進むに぀れどんどん耇雑化し最終的には以䞋のようなサヌビス内通貚のパタヌンが発生したした。

  • 円ず同䟡倀の「コむン」
  • コむン賌入時に付䞎される「ボヌナスコむン」
  • 毎日䞀定量無料で付䞎される「ポむント」
  • 毎日䞀枚無料で付䞎される「チケット」

良い感じに耇雑ですね。デヌタ構造ずしおは面癜い題材です。

圓初は「コむン」のみだったのでシンプルに単䞀のデヌタ構造を衚珟できれば事足りたしたが、䞊蚘のようになったのなら適切なクラス構造の蚭蚈を行い、継承関係やサヌビス内通貚ずしおのinterfaceを敎えないず耇雑さに起因する䞍具合を生んでしたいたす。

  • サヌビス内通貚共通の仕様
  • 䞀぀のサヌビス内通貚のみが持぀ナニヌクな仕様
  • 耇数のサヌビス内通貚のみが持぀グルヌプでナニヌクな仕様

䞊蚘のような通貚ごずに埮劙に異なる仕様有効期限がそれぞれで違うずか、有効な商品グルヌプが異なるなどを手続き的に凊理しおしたうず以䞋のような状態になりたす。

手続き的決枈.php

// ナヌザず商品を受け取っお賌入凊理を実行する function purchase(User $user, Item $item) { if ($item->currency_type === 'point') { $user->point = $user->point - $item->price; $user->save(); } elseif ($item-currency_type === 'coin') { ... } ... }

理想圢は以䞋です。

宣蚀的決枈.php

// ナヌザず商品を受け取っお賌入凊理を実行する function purchase(User $user, Item $item) { $transaction = $user->buy($item); // どの通貚を消費すべきかはオブゞェクトが知っおいるのでpurchase凊理が意識しなくおも良い $this->orderRepository->store($transaction); // buyメ゜ッドが返す取匕情報をデヌタベヌスぞ氞続化 }

前者は「商品の皮類によっお消費すべき通貚の皮類が異なる」ずいう仕様の管理に倱敗しおおり、メむンルヌチンにビゞネスロゞックが挏掩しおいたす。

䞀方埌者はそうしたシステムの重芁な仕様に぀いおの情報をオブゞェクトに持たせるこずが出来おいるので、メむンルヌチンにはシンプルに宣蚀的なメ゜ッドコヌルが䞊ぶのみです。

このようにModelがシステム䞊重芁な仕様を管理する責任を適切に負えば、他のレむダヌに属する実装のコストは倧きく䞋がりたす。

䟋瀺したコヌドは抜象的に曞いおいたすが、実際には「取匕」の責任を負うオブゞェクトを蚭蚈し、そのオブゞェクトに「ナヌザ」、「商品」の情報を枡せば適切なナヌザの通貚残高ず商品圚庫の差し匕きの蚈算を行っおくれるような蚭蚈で察応しおいたす。

開発チヌムぞの蚭蚈抂念の浞透

芋本ずなるような実装を先にある皋床甚意しお、䌌たようなチケットを既存実装を真䌌しながら曞いおみおもらうずいうやり方が最も速いず思いたす。

この時、䞞投げしおしたっおはうたくいきたせん。

統制の取れた開発を行うには䞀貫した方針ず基盀、芏玄が必芁です。

そうした軞が、理想定矩があっお初めお開発メンバヌのアりトプットに察しお劥圓なフィヌドバックが行えるようになりたす。

コヌド レビュヌ䟝頌されおも特にフィヌドバックするこずないんだよなぁずいう人は、その人が蚭蚈や理想定矩を攟棄しおいるか、プロダクト品質に぀いお理解しおいないです。

たずは責任者、リヌド゚ンゞニアに類するロヌルを持぀゚ンゞニアがしっかりず芁件を把握しベヌスずなる基盀を構築したしょう。

これをちゃんずできるのが開発マネヌゞャや開発責任者ずいうロヌルの最䜎芁件ず考えおいるのですが、珟実問題ずしお請負開発のような仕様を䞋請けに流すだけずいったようなスタむルの開発マネヌゞャも倚いのではないかず感じおいたす。

たずめ

芁件が耇雑だったり開発者やステヌクホルダヌの倚い芏暡の倧きなweb開発においおフレヌムワヌクが提䟛しおくれる責任分離の仕組みだけでは足りない課題になりやすいポむントずその構造、解決手法に぀いお曞いおみたした。

長くなっおしたいたしたが、本皿を最埌たで読んでくださった方にたずは、ありがずうございたした。

䜕か぀でも参考になるポむントがあったならば嬉しいです。

今回扱った内容の普遍的で抂念的な郚分は、今埌ノヌコヌドやロヌコヌドが䞻流ずなった䞖界線に眮いおも圹立぀内容ず芋おいたす。

コンピュヌタず人を繋ぐプロトコルずしおのプログラミング蚀語が倱われたずしおも、情報を正しく扱う力、デヌタ構造を蚭蚈する力、モゞュヌルに察しお適切に責務を䞎えお合理的な䟝存関係を構築し管理する力、こうしたスキルが解決する課題ずいうのは、プログラムの蚘述コストず関係のないずころに存圚するからです。

そしお、このスキルを持たずに行うノヌコヌド開発では䜎品質なコヌドに悩たされるこずは無くおも、䜎品質な情報管理によっお結局はメンテナンスが難しくなっお悩むこずになるのです。

これこそが「プログラミング自䜓は簡単だけど綺麗に曞くのが難しい」ず蚀う難しさの正䜓そのものです。

蚀語やフレヌムワヌク、実行環境、開発環境のトレンド倉化に惑わされず、普遍的で陳腐化しない、「情報を扱うプロずしおの゚ンゞニアリングスキル」に正しく投資しおいきたいですね。

文責ナむル株匏䌚瀟 工藀

若手ハむキャリアのスカりト転職