なぜ PSR 標準を使うのか?
NENE2 は独自の HTTP 抽象ではなく PSR-7・PSR-15・PSR-17 の上に構築されています。このページではその理由を説明します。
各標準がカバーする範囲
| 標準 | 定義内容 |
|---|---|
| PSR-7 | RequestInterface、ResponseInterface、StreamInterface — HTTP メッセージの形状 |
| PSR-15 | MiddlewareInterface、RequestHandlerInterface — ミドルウェアとハンドラーの合成方法 |
| PSR-17 | PSR-7 オブジェクトを生成するファクトリーインターフェース |
独自抽象を使わない理由
独自の Request クラスはすぐ書けてコントロールしやすい。しかしコストは後から現れます。
- 新しい HTTP ライブラリごとに独自アダプターが必要になる
- あるプロジェクト向けに書いたミドルウェアが別プロジェクトに持ち込めない
- テストに実行中の HTTP サーバーか独自クラス自体が必要になる
PSR-7 オブジェクトはイミュータブルな value object です。ServerRequestInterface を受け取り ResponseInterface を返すハンドラーは、それを呼び出すフレームワークに何の前提も置きません。
なぜイミュータブルなメッセージなのか
PSR-7 メッセージはイミュータブルです。withHeader()・withBody() などのメソッドは既存インスタンスを変更せず新しいインスタンスを返します。これにより、後続ハンドラーが検査するリクエストをミドルウェアが暗黙に変更してしまうバグのクラスが排除されます。
php
// 各ミドルウェアがクリーンなコピーを受け取る — 元のリクエストは変更されない
$request = $request->withAttribute('request_id', $id);なぜ PSR-15 ミドルウェアなのか
PSR-15 はミドルウェアのコントラクトをシングルメソッドで定義します。
php
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $next
): ResponseInterfaceこれが意味すること:
- PSR-15 ミドルウェアはどの PSR-15 パイプラインにも組み込める
- パイプラインの順序は明示的なコードであり、隠れたフレームワークライフサイクルではない
- ミドルウェアのユニットテストはモックの
RequestHandlerInterfaceだけで済み、実行中サーバーは不要
具体的なパッケージ選択
NENE2 はメッセージオブジェクトに Nyholm PSR-7、ミドルウェアディスパッチャーに Relay を使用します(ADR 0001 参照)。これらは標準を実装しつつ、フレームワーク固有の API を上乗せしない軽量パッケージです。
トレードオフ
| メリット | コスト |
|---|---|
| 相互運用可能なミドルウェア | 流暢な独自 API より冗長 |
| イミュータブルなメッセージでバグ削減 | with* 呼び出しのたびにオブジェクト生成 |
| サーバーなしでテスト可能 | PSR インターフェースの理解が必要 |
小規模な JSON API フレームワークにとって、相互運用性とテスタビリティのメリットは冗長さのコストを上回ります。