外部連携のフィールド、エラーコード、アクセス規則がコントローラー、アプリケーションサービス、モデル、業務プロセスに現れると、それはもはや技術的な詳細ではありません。その時点で、プロバイダーの変更、APIの更新、外部障害への対応には、そのシステムを知るべきではないアプリケーション部分の変更が必要になります。
PHPで外部連携を隔離するとは、明確な境界を設けることです。ドメインは必要なものを独自の言語で表現し、インフラストラクチャ層はその必要性をプロバイダー固有のプロトコル、形式、振る舞いへ変換します。別名のクラスの背後にAPIを隠すことではなく、その判断がアプリケーション全体を制約しないようにすることです。
連携がすでにアプリケーションを汚染しているとき

結合は通常、段階的に増大します。チームが緊急の対応のためコントローラーからAPIを利用し、その後別のフローが同じクライアントを再利用し、最終的にレスポンス配列とSDKの例外が業務の暗黙的な依存関係になります。
- ユースケースがプロバイダーのフィールド名を持つ配列を受け取る、または返す。
- 業務ロジックが
ERR_42やPENDING_REVIEWなどの外部コードを比較する。 - コントローラー、コマンド、キュージョブがHTTPリクエストを直接構築する。
- 外部SDKの例外がインフラストラクチャの外部で捕捉される。
- 認証情報、エンドポイント、バージョンの変更により複数モジュールの編集が必要になる。
- ドメインのテストに実際の接続、トークン、またはプロバイダーのモックレスポンスが必要になる。
これらの兆候は、全面的な書き直しを開始すべきことを意味するわけではありません。ただし、業務上の重要度、プロバイダー変更の頻度、内部コンシューマー数、データの機密性、障害からの復旧難易度というリスクに基づき、その連携を優先すべきことを示します。
業務言語から契約を定義する
ポートとも呼ばれる内部契約は、外部APIの操作カタログを再現するものではなく、アプリケーションが必要とする能力を記述しなければなりません。たとえば予約アプリケーションには、「予約を依頼する」「その状態を照会する」「それをキャンセルする」ことが必要です。ドメインが、プロバイダーがXML、OAuth、数値識別子、または固有のリトライ規約を使うことを知る必要はありません。
ポートはPHPインターフェースとして表現できます。
interface ReservationGateway
{
public function request(ReservationRequest $request): ReservationResult;
public function status(ReservationReference $reference): ReservationStatus;
public function cancel(ReservationReference $reference): void;
}契約の型は内部言語に属する必要があります。ReservationRequestには業務判断に必要なデータを含め、認証フィールド、HTTPヘッダー、プロバイダーから引き継いだ名前を含めるべきではありません。同様に、内部参照は外部識別子をカプセル化できますが、すべてのユースケースでそれを支配的にしてはなりません。
連携境界を構成する要素
ポート、アダプター、内部DTO
ポートはアプリケーションが利用するインターフェースです。アダプターは外部システムと通信する実装です。その間で、内部DTOがアプリケーションにとって安定した構造でデータを運びます。
アダプターは双方向に変換します。内部DTOを固有のリクエストへ変換し、レスポンスをドメインが解釈できる結果へ正規化します。プロバイダーがguest_countをtravellersに変更した場合、その変更はそのアダプター内に閉じ込める必要があります。
設定、認証情報、トランスポート
エンドポイント、トークン、タイムアウト、証明書、リトライポリシーはインフラストラクチャ上の関心事です。これらは設定を通じて注入し、エンティティやドメインサービスの外に置く必要があります。また、HTTPクライアントまたはSDKをアダプターから分離することも推奨されます。これにより、ライブラリの置換、テレメトリーの記録、実際のトランスポートに依存しないマッピングのテストが容易になります。
エラーの変換と不確定な状態
すべての失敗が同じ扱いになるわけではありません。プロバイダーが拒否したバリデーションエラーはユーザーにとって回復可能な場合があります。認証エラーには運用上の介入が必要です。タイムアウトは、プロバイダーがリクエストを処理済みである可能性があるため、不確定な状態を残すことがあります。
内部契約は、外部由来の例外を漏らすことなく、これらの違いを表現しなければなりません。たとえばアダプターは、バリデーションレスポンスをReservationRejectedへ、一時的な問題をTemporaryUnavailableへ、リクエスト送信後のタイムアウトをUnknownSubmissionStateへ変換できます。最後のものは単純なエラーとして扱ってはなりません。冪等性キーによる後続照会または運用上の照合が必要になる場合があります。
エラーを変換することは、詳細を消去することではありません。相関ID、技術的原因、関連レスポンスを安全に記録し、シークレットや機密データをユーザーに公開しないでください。
予約サービスをカプセル化する例
あるプロバイダーが、特定の形式の日付、独自のホテルコード、認可ヘッダーを持つJSONリクエストを要求するとします。内部ユースケースはそのリクエストを構築すべきではありません。予約リクエストを受け取り、独自の規則を適用して、ReservationGatewayを呼び出します。
アダプターExternalReservationAdapterは、次の固有の作業を行います。
- 内部の宿泊施設識別子をプロバイダーが認識するコードへ変換する。
- 外部契約に従って日付、宿泊者、希望を整形する。
- 認証情報と冪等性キーを追加する。
- HTTPコード、エラーボディ、独自の状態を解釈する。
- 内部の参照と状態を返す。
アプリケーションは予約がいつ受け入れ可能かという規則を保持し、アダプターはそのプロバイダーに予約を依頼する方法の規則を保持します。2つ目のプロバイダーを追加する場合、業務能力が同等であれば同じポートを実装できます。同等でないなら、共通インターフェースを無理に適用すると重要な差異を隠し、曖昧な判断を生むおそれがあります。
すでに結合した連携を抽出する方法
安全な移行のために、プロダクトの進化を止める必要はありません。まず棚卸しを行います。直接呼び出し、SDKクラス、外部形式、漏れ出た例外、コンシューマーを特定してください。最初に、クリティカルな経路または変更頻度が最も高い経路を特定します。
- ファサードを導入する:ポートと初期アダプターを作成し、既存クライアントの一部を一時的に再利用できるようにします。
- フロー単位でコンシューマーを移行する:一度に1つのユースケースについて直接呼び出しを置き換えます。同じエラーについて異なる2つの解釈を維持しないでください。
- マッピングを集中化する:コントローラー、サービス、テンプレートから外部フィールドおよびコードの変換を取り除きます。
- 可観測性を追加する:レイテンシー、結果、正規化されたエラー、内部リクエストと外部呼び出しの相関を記録します。
- 直接アクセスを廃止する:コンシューマーが残っていなくなったら、回帰を避けるため公開されたクライアントを制限または削除します。
移行中、ファサードをSDKメソッドの汎用コンテナーにしてはなりません。その目的は有用で安定した境界を定義することであり、結合を別のフォルダーへ移すことではありません。
隔離を検証するテストと基準
ドメインのテストではポートのテストダブルを使用する必要があります。これにより、ネットワーク、認証情報、プロバイダーの偶発的な振る舞いなしに業務判断を検証できます。一方、アダプターのテストでは、制御された環境、模擬サーバー、または外部システムが文書化した契約に対して、リクエスト、レスポンス、エラーのマッピングを確認しなければなりません。
次の基準を満たせば、結果は検証可能です。
- 形式、エンドポイント、SDKの変更がアダプターとその設定に集中している。
- ユースケースがHTTPクライアントや外部型ではなく、内部契約に依存している。
- プロバイダーの例外とコードが境界を越えない。
- 不確定な状態に、必要に応じた冪等性または後続照会を含む明示的な扱いがある。
- 業務テストはテストダブルで実行され、連携テストは実際の変換を検証する。
別のプロバイダーを追加する前によくある誤り

早すぎる抽象化はリスクです。実際の置換ニーズがない、安定した単一の連携のために複雑な階層を作らないでください。反対の極端も失敗します。外部API全体を内部インターフェースで再現すると、ドメインがその複雑さを継承します。
連携する前に、業務に必要な能力、各データを所有する者、アクション可能なエラー、操作の重複を防ぐ方法、レスポンスが届かない場合に何が起きるかを確認してください。その判断に基づいてポートを定義し、アダプターを変換器として実装し、外部固有の事情を境界に維持します。この規律により、プロバイダーごとの変更をアプリケーション横断の変更にせず、PHPで外部連携を隔離できます。



