Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応用

武田 憲太郎
April 23, 2025
44

 Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応用

第175回 PHP勉強会@東京
#phpstudy

2025-04-23 20:00 -
メイン発表枠(30分)

https://phpstudy.connpass.com/event/350509/

武田 憲太郎

April 23, 2025
Tweet

Transcript

  1. このトークを届けたい相⼿ 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 1 • 依存逆転原則の何が嬉しいのか(実は)わからないので知りたい • 疎結合な設計を⾃然に書けるようになりたい •

    初期設計や基盤のコードを書く⽴場になりたい • ライブラリのコードを⾃然に読めるようになりたい • OSSに限らず「ライブラリ」に相当するコードを書いてみたい これらをゴールに 「普段の流れ作業で書くコード」 の次のステップを紹介します
  2. 適⽤前の素朴な実装: コード class UserController { public function store(StoreUserRequest $request) {

    $mailer = new Mailer(); $mail = new UserRegistrationEmail($mailer); $user = User::create($request->validated()); $mail->sendTo($user); return new UserResource($user); } } メール送信 ユーザーへ送信するメール
  3. 依存逆転の原則 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 6 1. ⾼⽔準モジュール(ビジネスロジックなど)は、 低⽔準モジュール(データベースやファイルシス テムなど)に依存すべきではない。 2.

    両者は、抽象(インターフェースや抽象クラス) に依存すべきであり、具体的な実装に依存しては ならない。 プログラミング/依存性逆転の原則 - Wikibooks
  4. 依存逆転原則の適⽤: 抽象の定義 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 7 •依存先の抽象を定義 •インターフェースを 宣⾔ •ここでは実装は⾏わ

    ない interface MailerContract { public function send( string $to, string $subject, string $body, ): void; } interface UserRegistrationEmailContract { public function sendTo( User $user ): void; } 実装は次のページ
  5. 依存逆転原則の適⽤: 具象の実装 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 8 class UserRegistrationEmail implements UserRegistrationEmailContract

    { public function __construct( private readonly MailerContract $mailer, ) { } public function sendTo(User $user): void { $this->mailer->send( $user->email, '登録ありがとうございます', $this->body($user), ); } } 実装から抽象へ依存 連鎖する依存も抽象へ 依存先の抽象に応じた実装
  6. 依存逆転原則の適⽤: 抽象と具象の結合 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 9 // app/Providers/AppServiceProvider.php $this->app->bind( MailerContract::class,

    Mailer::class, ); $this->app->bind( UserRegistrationEmailContract::class, UserRegistrationEmail::class, ); • MailerContract契約が必要な場合 • Mailerクラスを使う • UserRegistrationEmailContractが必要な場合 • UserRegistrationEmailを使う
  7. 依存逆転原則の適⽤: 整理された依存 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 10 •抽象に依存した •インスタンス⽣ 成の責務を外に 追い出した

    class UserController extends Controller { public function __construct( private readonly UserRegistrationEmailContract $mail, ) { } public function store(StoreUserRequest $request) { $user = User::create($request->validated()); $this->mail->sendTo($user); return new UserResource($user); } }
  8. 依存逆転原則の適⽤: 得られたもの 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 12 • Before: UserControllerがあらゆるクラスに依存 •

    After: UserControllerはただ1つの抽象に依存 • Before: UserControllerは⽣成の責務を持つ • After: UserControllerは⽣成の⽅法を知らない 得られたもの: • 適切な責務配置 • テスト容易性 • 変更容易性 堅牢で変更に強い設計が得られた
  9. 「典型的な例」への個⼈的な所感 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 15 • 責務が再配置された、が • 意味のわからない「オマジナイ」を書かされる(オンボーディングコスト) •

    ファイル(読むべきコード)が急に増えた • 抽象への依存により疎結合になった、が • 実装へのコードジャンプが困難になった • 何をするにも「2箇所」の修正が必要 • テストが容易になった、のか?? • 具象クラスも普通にモックできるのでは? • 具象の差し替えが不要な場合ますますメリットがわからない(DIだけで事⾜りる) • 依存管理をDIコンテナに局所化できた、が • DIコンテナへの登録が神設定ファイルになったのだが… 得られたメリットと同じくらいデメリットを⽀払っていないか?
  10. 「典型的な例」への個⼈的な所感 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 16 • 原則⾃体は合理的だと思う • 「典型的な例」を適⽤しただけではメリットが少ない •

    概念の説明としては適切かつシンプル • あくまで「基本形」の説明に過ぎない • そのまま実践して良いかは別の話 実践でメリットを得るには 少しの⼯夫と応⽤が必要
  11. Inertia.js インストールと起動 # Laravel Installerを最新版に更新 $ composer global update laravel/installer

    ## またはインストール # composer global require laravel/installer # プロジェクトの作成とビルド $ laravel new --react --phpunit --npm starterkit $ cd starterkit $ npm run build:ssr # 開発サーバを2つ起動 prompt-1 $ ./artisan serve prompt-2 $ ./artisan inertia:start-ssr # ブラウザで開く $ open http://localhost:8000
  12. Next.js型フルスタックアーキテクチャ 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 21 1. 初回ロードはフルページ遷移(SSR) 2. 画⾯遷移時はSPA遷移(CSR) 画⾯遷移の⽅法に応じてレスポンスが変わる:

    • SSR: propsでcomponentをレンダしたtext/html • inertia:start-ssrより起動されたサーバサイド JavaScript(Node.js)が画⾯をレンダ • CSR: ルート情報とpropsを含むapplication/json • ブラウザ側のJavaScriptが画⾯をレンダ
  13. Inertia.js コントローラーからの使い⽅ • 「テンプレートと変数を指定」という典型的な作り • これだけでCSR/SSR両対応レスポンスになる ここで返しているResponseは何者? public function create(Request

    $request): Response { return Inertia::render('auth/login', [ 'canResetPassword' => Route::has('password.request'), 'status' => $request->session()->get('status'), ]); } テンプレート 変数
  14. Inertia.jsの「レスポンス」 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 24 • Response::toResponse()がレスポンスを切り替え toResponse()はどこから呼ばれている? public function

    render( string $component, $props = [] ): Response { // 省略 return new Response( // 省略 ); } class Response implements Responsable { public function toResponse($request) { // 省略: `$component`や`$props`から「ページ情報」を生成 if ($request->header(Header::INERTIA)) { return new JsonResponse( $page, 200, [Header::INERTIA => 'true'] ); } return ResponseFactory::view( $this->rootView, $this->viewData + ['page' => $page] ); } } return Inertia::render(/**/); CSR:カスタムヘッダ付きXHR SSR:トップレベルナビゲーション
  15. Laravel Frameworkの「レスポンス」 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 25 • コントローラーから返す(Laravelに渡す)「レスポンス」は mixed‒何でも良い •

    Responsableだった場合LaravelがtoResponse()を呼ぶ • Responsableは「何か」をSymfony Responseに変換するインターフェース /** * @param ¥Symfony¥Component¥HttpFoundation¥Request $request * @param mixed $response * @return ¥Symfony¥Component¥HttpFoundation¥Response */ public static function toResponse($request, $response) { if ($response instanceof Responsable) { $response = $response->toResponse($request); } // 省略 } interface Responsable { /** * @param ¥Illuminate¥Http¥Request $request * @return ¥Symfony¥Component¥HttpFoundation¥Response */ public function toResponse($request); }
  16. パッケージを跨いだ依存逆転 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 27 • ⼊⼒(例: DIによる注⼊)ではなく出⼒(例: return)の依存を逆転 •

    ⾃分たちが依存先を作成するのではなく既存の依存先を利⽤ • 依存元を⾃分で「呼ぶ」のではなくフレームワークに「呼ばせる」
  17. 「典型的な例」との⽐較 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 28 • 典型的な例「コントローラーは◯◯に関知しない」 • 関知せずに済むための追加の「オマジナイ」が必要 •

    (結局、関知しているのでは?) • 今回の例「コントローラーは表⽰に関知しない」 • 「オマジナイ」をライブラリ層に隔離した • コントローラー内での追加のコードは不要 • (単純なコードの置き換えで完結)
  18. Symfony StreamedResponse 通常のレスポンス: • new Response()の際に全⽂を ⽣成 • ⻑さはmemory_limitにより制 限される

    StreamedResponse: • ⽣成結果を順次echo等で出⼒ • 全⽂を保持する必要はない • ⽣成処理はフレームワーク側で 処理の最後に⾏われる(コント ローラーからクロージャを指定 し遅延評価させる) class FibonacciNumberController { /** * フィボナッチ数を改行区切りで100万個レスポンス */ public function index(): StreamedResponse { return new StreamedResponse(function () { $prevs = [new Number(0), new Number(1)]; for ($i = 0; $i < 1_000_000; ++$i) { echo ($npm = $prevs[0] + $prevs[1])."¥n"; array_push($prevs, $npm); array_shift($prevs); } }); } }
  19. StreamedResponse 依存関係 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 31 • callableという「抽象」に依存することで「出⼒を ⽣成する責務」と「出⼒する責務」を分離 •

    $callbackを⾃分で「呼ぶ」のではなくフレーム ワークに「呼ばせる」 public function __construct( ?callable $callback = null, int $status = 200, array $headers = [] );
  20. Laravel prunable: モデルの整理 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 32 • データベースから不要レコードを⾃動的に削除する Eloquentの機能

    • ユーザーランドからは「削除する条件」をクエリと して返却 • 返却に基づきLaravelがそれらを⼀括削除 • 全てのEloquentモデルを⾛査し⼀括で処理してくれる • 単に削除するだけでなく、専⽤イベントの発⽕や論理削 除の処理なども適切に⾏う
  21. Laravel Prunable 利⽤⽅法と依存関係 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 33 class User extends

    Model { use Prunable; public function prunable(): Builder { return static::query()->where('last_login_at', '<=', now()->subMonth()); } } traitの利⽤を宣⾔し 整理(削除)する条件を指定
  22. まとめ 2025-04-23 #phpstudy Inertia.jsのサーバサイド実装から学ぶ依存逆転原則の応⽤ 35 • 依存逆転原則を適⽤することで: • 依存の向きをコントロールしコンポーネントを疎結合に保てる •

    クラス数の増加や可読性低下などのデメリットを招くことがある • 依存逆転原則を上⼿に適⽤することで: • デメリットを「普段触らない場所」に追いやれる • 「原則」とは: • 「多くのケースに適⽤できる⼀般的な解法」に過ぎない • 個別のケースでメリットを得られるかは状況次第 • 「原則」を適⽤する場合は: • 「どんなメリットを得られるか?」を具体的に考えると良い • デメリットを上⼿に避ける⽅法を考えると良い