コンテンツへスキップ
DedicatedPHP 接触

デリバリーを止めない共有PHP契約の進化

共有コンポーネントの変更、コンシューマーの移行、APIの管理された廃止で、PHPの後方互換性を適用するためのガイド。

アダプター、コンシューマー、移行フェーズを示す共有PHP契約の編集用図

共有PHPライブラリに対する一見小さな変更でも、独立したデリバリーを停止させる可能性があります。パラメータ名の変更、デフォルト値の変更、例外の置き換えは、今日デプロイされないコンシューマー、別リポジトリに存在するコンシューマー、またはコンポーネントを間接的に呼び出すコンシューマーを壊すことがあります。障害は実行時、非同期タスク内、または変更前に生成されたデータのデシリアライズ時に発生する可能性があります。

PHPの後方互換性とは、過去のすべてのインターフェースを維持することではありません。これは、プロデューサーとコンシューマーが異なる速度で進化できるようにし、明示的な移行期間と検証可能な廃止を設けるための規律です。目的は、強制的な協調デプロイと、廃止済みAPIの恒久的な蓄積の両方を避けることです。

内部契約に含まれるものを特定する

内部契約に含まれるものを特定する — guía visual de DedicatedPHP

内部契約とは、外部APIとして公開されていなくても、別のモジュールが依存するあらゆる振る舞いです。Composerの依存関係とPHPインターフェースは可視的な一部ですが、それだけで範囲が尽きるわけではありません。共有コードを変更する前に、少なくとも次の要素を確認してください。

  • 公開シグネチャ:メソッド名、パラメータ、順序、型、null許容性、デフォルト値、戻り値の型。
  • セマンティクス:各引数の意味、必須フィールド、特定の条件において期待される結果。
  • エラー:送出される例外、エラーコード、クライアントが処理するメッセージ、nullまたは空の結果。
  • データ:配列キー、JSON構造、キューメッセージ、ドメインイベント、シリアライズ済みファイル、永続化データ。
  • 副作用:イベント送信、データベース書き込み、キャッシュ無効化、HTTP呼び出し、実行順序。
  • 運用上の振る舞い:リトライ、冪等性、タイムアウト、一時的障害の処理。

たとえば、JSONレスポンスへのフィールド追加は通常は追加的変更ですが、コンシューマーがプロパティの閉じたリストを検証している場合はそうではありません。同様に、より具体的な例外は技術的には正しくても、コンシューマーがリカバリーを有効にするために以前の例外を捕捉している場合は互換性がありません。

実装を書く前に変更を分類する

分類により、設計上の決定が本番インシデントになることを防げます。既知のコンシューマーと退出戦略とともに、変更提案に記録することが望まれます。

追加的変更

既存経路を変えずに新しい機能を取り入れます。たとえば、新しいメソッド、中立的なセマンティクスを持つオプションパラメータ、追加イベント、新しいバージョンのメッセージです。コンシューマーが個別にデプロイされる場合、これが望ましい選択肢です。新しい経路は以前の経路と共存可能でなければならず、従来の振る舞いは検証可能な形で維持されなければなりません。

適応により互換となる変更

変換レイヤーにより以前の結果を維持できます。たとえば、古いインターフェースは引数と結果を変換して新しいサービスへ委譲できます。適応は、それが局所化され、廃止日が設定され、かつコンシューマーが意識して判断すべきビジネス上の差異を隠さない場合に意味を持ちます。

非互換または不確実な変更

メソッドの削除、型の厳格化、状態の意味の変更、永続化フォーマットの変更は、通常非互換です。コンシューマーの信頼できるインベントリがない変更も、不確実として扱うべきです。どちらの場合も、新しいパッケージバージョンを公開するだけでは不十分です。移行、計画されたマイグレーション、または分離された契約バージョンが必要です。

検証可能なコンシューマーインベントリを構築する

テキスト検索だけに基づいて判断しないでください。コンポーネントは、依存性コンテナ、設定、リフレクション、イベント、キュー、HTTP統合を介して別のコンポーネントへ到達することがあります。インベントリは静的な証拠と代表的な実行を組み合わせる必要があります。

  1. Composerで宣言された依存関係、バージョン制約、パッケージをインストールするリポジトリを確認します。
  2. クラス、インターフェース、メソッド、イベント、設定キー、メッセージ形式の直接使用を検索します。
  3. ファクトリ、コンテナ定義、リスナー、コマンド、cron、worker、インフラストラクチャアダプターを調査します。
  4. 決済、認証、注文、同期、通知、リカバリープロセスというクリティカルパスを特定します。
  5. 各コンシューマーについて、所有者、使用バージョン、移行経路、変更完了の証拠を記録します。

ライブラリの公開とアプリケーションのデプロイは別のアクションです。互換バージョンを公開すれば、各コンシューマーは準備ができた時点で更新できます。すべてのコンシューマーを同時にデプロイすると、通常の進化が脆弱な組織的依存関係に変わります。

適切な境界で追加的進化とアダプターを適用する

新しい要件がモデルを変える場合は、まず新しい機能を導入し、一時的に以前の機能を維持してください。変換が一意に定まる限り、レガシーインターフェースは新しい実装へ委譲できます。これにより、コンシューマーは単一の移行期間を調整せずに移行できます。

interface LegacyPriceCalculator
{
    public function calculate(int $amount): int;
}

final class LegacyPriceCalculatorAdapter implements LegacyPriceCalculator
{
    public function __construct(private PriceCalculator $calculator) {}

    public function calculate(int $amount): int
    {
        return $this->calculator->calculate(new Money($amount, 'EUR'))->amount();
    }
}

アダプターは通常、ドメインの中核ではなく、契約間の境界に属します。ドメインは現在のモデルを表現すべきであり、古い引数、センチネル値、履歴上の形式の変換は専用レイヤーに置くべきです。ドメインがクライアント世代ごとの条件を保持すると、履歴上の複雑さが将来のすべての変更に伝播します。

情報損失や新しいビジネス上の判断がある場合は、アダプターを無理に適用しないでください。古い契約に新しい振る舞いに必要なデータが含まれない場合は、移行期間中は両方の契約を維持するか、コンシューマーに追加情報を明示的に要求してください。

非推奨化を管理された廃止に変える

代替手段、期限、責任者のない非推奨APIは、非推奨化ではありません。それは追跡されない負債です。有効な廃止には、コード上のシグナル、移行手順、削除条件、可能であれば使用状況の観測を含める必要があります。

  • レガシーメソッドまたはクラスを明確なドキュメントでマークし、該当する場合はtrigger_error(..., E_USER_DEPRECATED)で制御された警告を出します。
  • セマンティクス、エラー、デフォルト値の差異を含め、正確な代替手段を示します。
  • 検証可能な退出条件を定義します。インベントリ化されたすべてのリポジトリの移行完了、観測された呼び出しの不在、または特定バージョンのサポート終了です。
  • 進捗を確認し、条件が満たされた時点でレイヤーを削除する責任者を割り当てます。

集約戦略なしに高ボリューム経路で無差別に警告を出すことは避けてください。ノイズによって重要なシグナルが隠れ、運用コストが増大する可能性があります。可観測性は、どのコンシューマーが以前の契約を引き続き使用しており、どの経路で使用しているかという具体的な問いに答えるべきです。

移行をテストし、デリバリーシーケンスを実行する

コンポーネントのユニットテストだけでは、コンシューマーが引き続き機能することを証明できません。各コンシューマーが必要とする入力、出力、エラーに対する契約テストを追加してください。古いインターフェースがサポートされている間はその回帰ケースを維持し、欠落値、以前のシリアライズ済みペイロード、期待される例外を明示的にテストしてください。

安全なシーケンスは通常、次の順序に従います。

  1. 以前の経路を維持したまま、新しい契約または追加的実装を公開します。
  2. リスクが正当化される場合は統合テストを使用し、コンシューマーを個別に更新・デプロイします。
  3. エラー、非推奨警告、レガシーインターフェースの使用状況を観測します。
  4. 移行インベントリを確認し、検出された間接コンシューマーを解決します。
  5. アダプターまたは古い契約を別のデリバリーで削除し、その不在を確認するテストを行います。

変更承認のためのチェックリスト

変更承認のためのチェックリスト — guía visual de DedicatedPHP
  • 影響を受ける契約は、PHPシグネチャを超えて定義されていますか。
  • 変更は追加的、適応可能、非互換、不確実のいずれかに分類されていますか。
  • イベント、データ、間接経路を含むコンシューマーのインベントリはありますか。
  • 解決策は同時デプロイを要求することを避けていますか。
  • アダプターがある場合、それはドメイン外にあり、廃止が予定されていますか。
  • 従来の振る舞い、新しい機能、期待されるエラーはテストされていますか。
  • 非推奨化には代替手段、廃止条件、責任者が示されていますか。
  • APIを削除する前に隠れた依存関係を検出するシグナルはありますか。

正しい判断は、互換性を無期限に維持することでも、全面的な調整を強いることでもありません。必要なものを維持し、証拠に基づいて移行し、履歴上の互換性が安全性をもたらさなくなった時点で削除するという、境界を持つ移行を設計することです。

これらのアイデアをあなたのプロジェクトに活用してみませんか?あなたのPHPプラットフォームについて話し合いましょう。
関連サービスを見る