2024.11.05「追記1:問題編」を追加しました!
2024.11.06「追記2:回答編」を追加しました!
2024.11.06 [補足]を追加しました!
前提
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台のサーバーが自分で出したキューを自分で処理するようにしか作られていません。
[補足]
ここはbrefを使用してサーバーレスLaravelにすると(従量課金になりますが)ほぼ全て解決します。
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
の中で使用する下位レイヤーに位置するものです。
構成変更やデータ移行は必ず発生するため、テーブルがどのように使用されているかを把握することはかなり重要なのですが、そこらじゅうでクエリが書かれているとその把握ができなくなり、最終的にテーブルを一切さわれなくなります。
[補足]
想像がつかないなら、謎関数のギッチリ詰まった神Excelを想像して頂ければそれに近しいものかと思います。
もうこうなったら詰みです。
リプレイスしか安全な方法が無くなってしまい、そのコストは計り知れません。
実際企業はコストを払ってくれませんから、エンジニアがサビ残をして不眠不休で作業することになる場合が多いと思います。(特にリーダー)
ではどう作ればいいのか?
サービスのあるべき姿はこういうものです。
左から Interface、method、model、ResourceModel という階層分けになってます。
そうです、これってLaravelとか全然関係なく、ただの関数セットになります。
リソースに紐づかないmodelがあるのはユーティリティ関数とかです。
こういうものを作るのはリポジトリパターンと一致しますが、私はこれを本体として「サービスSDK」と呼んでいます。
そして、これをこうするわけです。
[補足]
初学者にどう説明すれば分かりやすいのか難しいですが、言うなればLaravelまでを「フロント」、サービス層を「バックエンド」として分離した形にし、バックエンド機能の全てをSDKの中だけに閉じ込めてしまいます。
Laravelまでを「フロント」とする理由はViewを扱うからであり、分離することでViewの構成に左右されず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ならテストもしやすいですし、取り回しが効いてとても使いやすいはずです。
追記1:問題編
いまいち伝わってないので追記。
問題4:「ディレクトリなんて好きに切れ」と言えない構造
Laravelはルート配下に訳分からんディレクトリが沢山あり、どこにディレクトリを切るのが正解なのか分からない上、artisanコマンドを叩くと後からディレクトリがボコボコと出来るため、どんな名前なら後からバッティングしないのかが全然読めないんですよ。
だから運営に予約語として関数置き場を設定することを求めないといけない。
別に常にボコボコとディレクトリが出来る訳でもないので設定を済ませてから切ってしまえばそれで良いのですが、何となく不安が残るため位置がフラフラしてしまうんです。
実際、各チームでかなり試行錯誤してます。
JSならルートに設定関係とpublicがあって、src配下に作っていけばいいので迷うことはないのですが、どうしてここは同じようにできないのかと思います。
私は app/
が jsで言う src/
に当たるのかと思いましたが、いや全然そんなこともなく..
それに、私みたいにAPI用途にしか使わない人には resources/
も js や tailwind の設定ファイルも邪魔なだけで、どうしてそっちをオプションにしないでAPIルートをオプションにするんだ!と真面目にキレたりしています..
sail:install
みたいに使うものを選べるならそうして欲しいんですけどねぇ..
問題5:簡単に「他のFW使え」とも言えない状況
LaravelはPHP界では唯一神になってしまいました。
なので「だったらSymphony使えばいいじゃん」と言われても「Symphonyエンジニア募集!で人集まると思うんか」となってしまいます。
別に言語を変えれば色々選べますが、Eloquentが使いやす過ぎるのでSQLを扱う場合はなかなか乗り換えできずにいます。
問題6:豪勢なお出迎えで魔改造の道へ導かれる
そもそもの話、今の世代にMVCでDB操作しながら画面レンダさせようとしてくるのが「いつの時代だよ」って話なんですよ。
今どき画面はSPAで組んだ方が超絶スムーズだし、何も魔改造する必要がないのです。
ライト層なら尚更だと思います。
これにはVueが付属した初期から「何で普通のSPAにさせてくれないんだ」と悶絶していました。
基本的にLaravelは装備の手厚さがお節介となり、執事とメイド付きで魔改造の道へ歩ませようとしてきます。
安全な汎用関数置き場が設定されてないのもそうです。
だからちょっとカスタムする障壁が異様に高い。
結果的にオレオレ魔改造の道へ誘導してしまっている。
結果、大きな一本糞が出来上がる。
たぶんMVCの設計思想が強く、それをアイデンティティにしているのでしょう。
サーバールーティングで画面をレンダさせる方向へ、割と強い力で誘導されます。
その誘導に気付かず使うと罠にハマるし、強引に振り切ろうとしても罠にハマる。
色々言われていますが、こんなに強く誘導されるフレームワークってありますか?
それも尖った部分を隠して攻めてきて、気づいて見渡したら根本から全部そうなってた…みたいな、エンジニアとしては最悪の体験をすることになる。
この記事を書くきっかけになったのも、私が SPA + API 強硬派なので、apiルートをオプションにされて完全に縁を切られたような気持ちになったからです。
追記2:回答編
いくつか頂いた質問の回答を並べます。
代替案を出せ
代替案無しで叩いたとは思っていなくて、サービスとしてバックエンドSDKを書けば後は軽量なHTTPルーターを使えばAPIは作れます。
Eloquentは使いたいので、だからこそLumenがサ終したことに絶望しています。
* 過去使った時は何故かうまいこと使えなかったが..
具体的には、私はJSでExpressを採用しています。
バックエンド側は AWS SDK のようにサバクラ式のSDKにしており、クライアントからLambdaを直接叩いて各インターフェースを構成しています。
さらに、SDKバックエンドのルーターに関してはJSONの受け渡しだけなので自作の簡単なルーターで分岐させています。
SDK全体図
|Client| <-> |API(自作ルーター)| - |各種メソッド|
各種メソッドの部分は別のLambdaを起動する場合もあるため、こうすることでClientに付ける lambda:invoke権限 が API 部分へのもののみなるようにし、APIにはサービス内リソースのすべての実行権限を付けています。
SDKって何ですか?
ggrks。AWS SDK とか Google Cloud SDK とかあんだろ 笑
そんな親切なフレームワークは存在しない
それはごもっともです。
ですが、Laravelは総合型故に誘導の力が強く「そう言う感じでやりたくないんだけどなぁ..」と思いながら従ってしまう部分があると思っており、そこに疑問を感じています。
そしてそこを突破しようとすると一気に情報が無くなる…
私は過剰な親切を求めているのではなく、むしろHTTPルーターならルーターとミドルウェアだけの方が使いやすく、ORMならORM単体で設定できる方が使いやすいのです。
それ以上のお膳立てがあるから「誘導」と言うヘイトに繋がってしまうんじゃないかと思います。
サーバーレスの時代になり「Laravelをフルに使って作りました!」と言われたら逆に頭を抱えられてしまうように時代が移っている感じもしています。
私的にLaravelはWEBの色々な素晴らしい作り方を示してくれましたが、今後プロジェクトで積極採用することは無くなり、初学者のWEB入門テキストにとどまってしまうのかなと感じています。実際、教材としては素晴らしい。
まぁそうなって欲しいところですが、この業界あるあるを考えると、またいつかイキリ倒してシャア増やす時が来るんだろうなぁ…白目
時代は速度を求めた
それは確かにあると思います。
それならDDDを artisan make でポンと作れるようなフレームワークを作れば良いじゃないかと思い、HTTPルーターとは関係の無い「マイクロサービスを作るためのフレームワーク」を私は実際に作りかけていました。
それはさておき、速度を求めるならアプリ単体を小さくまとめて、会計ソフトのfreeが提唱するように経営側からスモールビジネスを打ち出し、小さなアプリの集合でサービス全体を形成するような戦略が必要になります。
実際にfreeのサービスはすべて単機能で別のURLにホストされています。
残念ながら多くの企業は、最初は「簡単なものでいいからペロッと作ってくれ」と依頼してきますが、後から後から機能追加が来て、最終的には一体どの機能で稼いでいるのか把握できないくらいモリモリに仕上げようとしてきます。
もっと言うと、開発人数は据え置きでメンテもさせてくれません。
なので私は最初からマイクロサービスで組み上げ、アプリ側で使うものを選択できるようなSPA + API構成で作成します。(マイクロサービス化 / ブロッキング)
そして更に、 "出来るだけ" 外部依存を取り除き、言語能力だけでサービス側を組み上げることに挑戦しています。(長寿命化してコードを枯れさせる)
ゆうてそこまで潔癖なタイプのエンジニアでは無いですし、型強硬派でもないです。
地雷を踏みすぎて丸焦げになった、ただの弱小リーダーです。
チームにコーダーがつくと入れ替わり立ち替わりまあ色々やってきますが、時間をかけて補正していければいいやと楽観的にやっています。
確かに速度面で言うと工数は多くなりますが、パターンが決まればそんな言うほどか?って程度の工数に抑えられています。
テストがしやすいので結果的に同じじゃね?くらいに思っています。
時代はそのように、また求めるものを変えているのかも知れません…
AIネイティブな時代に進むと、OSSパッケージすら不要になり、クソ長持ちするCOBOLとかで何でも作ってしまうような事もありえない話ではないのかなと思います。
筆者は最近COBOLを知り、その見通しの良さに感銘を受けました。
それを受けて動作ステップの見通しの良さが如何に重要であるかを知りました。
全部大文字で書かれたコードのかっこよさヤバイで(性癖)