前提
LaravelはWordPressからステップアップしたい人に丁度フィットしたような作りになっており、オンプレ前提であり、MVC構成の簡単なSSR(サーバーサイドレンダリング)を推しています。
WordPressの次のステップと捉えると納得できますし、小さなアプリを簡単に作るには丁度良いと思います。
しかし、これで大きなサービスを作ろうとすると途端に崩壊します。
基本的にドキュメント通りに作成すると画面とインターフェースが密結合し、サービスとしてのインターフェースが固まらない状態になります。
結果的に私が関わったプロジェクトは全て密結合で触れない状態に陥っていました…
たぶん日本中、いや世界中がこうなってると思います。
決してエンジニア個人の責任ではなく、これはコミュニティやLaravelの罪だと考えています。
Laravelはオンプレ前提
今はLaravelを本番環境で稼働させようとするとこうなると思います。
|WAF| - |LB| - |Nginxコンテナ| - |Laravelコンテナ| - |DB|
サーバーレスとコンテナのオーケストレーションですね。
ではスケジューラーはどうやって動かしますか?
Laravelコンテナにどうやってcronで毎分 artisan schedule:run
を叩くよう実装するでしょう。
LBで分散しているのでコンテナは複数稼働しています。
どうやって1つだけが実行されるようにしますか?
書き出されたログはどうやって読めばいいでしょう?
make:command
で作ったartisanコマンドはどうやって叩きますか?
そう言うことがドキュメントに書かれていないのは、そもそもLaravelがオンプレ前提だからなんです。
キューの構造をとっても名前空間指定で生成されるため、別サーバーのLaravelにキューを渡せるようには作られておらず、1台のサーバーが自分で出したキューを自分で処理するようにしか作られていません。
MVCフレームワークであると言うこと
Laravel 11.x から routes/api.php
はデフォルト構成から消えました。
これを使うには php artisan install:api
を叩く必要があります。
これは明確な意思表明です。
LaravelはMVCフレームワークであり、SSRの方面に進んでいきます。
だからドキュメントを探しても SPA + API 構成にたどり着けなかったんですね…
これにInatiaという謎技術が合体し、密結合した一本糞にしてしまってるチームは少なくないはずです。
誰も悪くありませんよ、ドキュメントに従っただけですから…
MVCフレームワークでは小さなアプリしか作れない
Laravelの信号の流れとしてはこんな感じ。
この Controller
の中では、 $request
からパラメーターを取り出し、 Model
を使ってデータベース操作をしてresult
(結果)を返します。
これは非常にシンプルで簡単に実装できますが、これで中規模以上のサービスを組もうとすると場当たり過ぎてだいたい破綻します。
問題1:コントローラーメソッドは使い回しできない
基本はHTTPルーターですから、入力がHTTP依存の特殊形式になっています。
下手にこのメソッドを動かすとミドルウェアまで動いてしまいますよね。
はい、このメソッドは使い回しできません。
ではどこに使いまわせるパターンを書きましょう?モデルに書きますか?
モデルに書いてしまうとモデルが過剰に肥大化してしまいます。
テーブルに依存しない処理はどこに書きましょう?
結論、Laravelはそういうサポートを提供していません。
自力でフォルダ分けをして関数セットを内包するクラスを定義するしかありません。
そしてこれらはほぼ闇落ちします…
これが大きなサービスを作れない1つ目の理由です。
問題2:入力がHTTP形式のメソッドしか無い
単純にテストしにくいんですよ。
普通に関数セットを定義していればテスト、いやそれ以前に実行確認も簡単です。
関数内で $request
からパラメーターを取り出してゴニョゴニョしていると入力をHTTP形式にせざるを得ない。
これはクリーンアーキテクチャ的にはアンチパターンであり、HTTP形式の入力がある部分は プレゼンテーション層 と呼ばれ、値の受け渡しのみしかしてはいけない場所です。
つまり、$request
からパラメーターを取り出し、そのパラメーターを他の関数の入力に渡して結果をそのまま返す。
それ以上のことをしてしまうと中規模以上のものを作ろうとした時に破綻してしまいます。
問題3:ORMをModelとしている
これが非常に良くないです。
いや、MVCで簡単に実装する分には便利なのですが、中規模以上のものを作る時の肝は 「サービスのインターフェースを固めて整理すること」 なので、何でも出来てしまうORM(クエリビルダ)をサクッと呼び出してホイホイ使ってしまうとインターフェースが永遠に固まりません。
私が命名するならこれは ResourceModel
であり、Model
の中で使用する下位レイヤーに位置するものです。
構成変更やデータ移行は必ず発生するため、テーブルがどのように使用されているかを把握することはかなり重要なのですが、そこらじゅうでクエリが書かれているとその把握ができなくなり、最終的にテーブルを一切さわれなくなります。
もうこうなったら詰みです。
リプレイスしか安全な方法が無くなってしまい、そのコストは計り知れません。
実際企業はコストを払ってくれませんから、エンジニアがサビ残をして不眠不休で作業することになる場合が多いと思います。(特にリーダー)
何であれ、夢も希望もありまてん 泣
ではどう作ればいいのか?
サービスのあるべき姿はこういうものです。
左から Interface、method、model、ResourceModel という階層分けになってます。
そうです、これってLaravelとか全然関係なく、ただの関数セットになります。
リソースに紐づかないmodelがあるのはユーティリティ関数とかです。
こういうものを作るのはリポジトリパターンと一致しますが、私はこれを本体として「サービスSDK」と呼んでいます。
そして、これをこうするわけです。
Laravelを使用する場合はViewの処理が入っている場合もありますが、それはLaravelのコントローラーメソッドでやってください。
こうした場合に守って欲しいのは、絶対にLaravelのコントローラーメソッドでModelを使わないこと、実装を書かないこと。(封印したカオスが目覚めてしまう!)
できるならDBクラスとかも封印したいですが、Laravelを使う以上そこまではできません。
Composerの名前空間でSDKの中身を呼び出せるのも微妙に破綻してます。
PHPで完全にprivateなクラスって定義できないんでしょうか…
APIでマイクロサービスを作る場合はもうLaravelである必要も無いでしょう。
軽量なルーターで構いませんし、いちいちCollectionにラップしないとまともに操作できないの面倒ですからJSとかGoとか、そう言うのでいいです。
Lambdaサポートされてる言語ならサーバーレスもやりやすいでしょう。
SDKはパッケージで作成することを勧めますが、別にLaravelの中にpackages/
とか作っていいと思います。
Eloquentを使うならconfig周りの実装が面倒なのでLaravelの中に作った方が省エネだと思います。
結果、2部構成になってしまいましたが…
はい、面倒ですよね。申し訳ないです…
密結合を排除してインターフェースを整理するとこうなってしまうんです。
なので「爆速フレームワーク」とか全く信用してません 笑
SDKならテストもしやすいですし、取り回しが効いてとても使いやすいはずです。
追記
いまいち伝わってないので追記。
問題4:「ディレクトリなんて好きに切れ」と言えない構造
Laravelはルート配下に訳分からんディレクトリが沢山あり、どこにディレクトリを切るのが正解なのか分からない上、artisanコマンドを叩くと後からディレクトリがボコボコと出来るため、どんな名前なら後からバッティングしないのかが全然読めないんですよ。
だから運営に予約語として関数置き場を設定することを求めないといけない。
別に常にボコボコとディレクトリが出来る訳でもないので設定を済ませてから切ってしまえばそれで良いのですが、何となく不安が残るため位置がフラフラしてしまうんです。
実際、各チームでかなり試行錯誤してます。
JSならルートに設定関係とpublicがあって、src配下に作っていけばいいので迷うことはないのですが、どうしてここは同じようにできないのかと思います。
私は app/
が jsで言う src/
に当たるのかと思いましたが、いや全然そんなこともなく..
それに、私みたいにAPI用途にしか使わない人には resources/
も js や tailwind の設定ファイルも邪魔なだけで、どうしてそっちをオプションにしないでAPIルートをオプションにするんだ!と真面目にキレたりしています..
sail:install
みたいに使うものを選べるならそうして欲しいんですけどねぇ..
問題5:簡単に「他のFW使え」とも言えない状況
LaravelはPHP界では唯一神になってしまいました。
なので「だったらSymphony使えばいいじゃん」と言われても「Symphonyエンジニア募集!で人集まると思うんか」となってしまいます。
別に言語を変えれば色々選べますが、Eloquentが使いやす過ぎるのでSQLを扱う場合はなかなか乗り換えできずにいます。