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

アプリケーションを書き直さずPHPの保守されていないパッケージを置き換える

保守されていないPHP依存関係を検出し、影響を抑え、テスト、契約、ロールバックを用いて置き換えるための段階的な計画です。

アダプター層とテストを通じて保守されていない依存関係を置き換えるPHPアプリケーションの編集用図解

保守されていない依存関係が直ちにインシデントになるわけではありませんが、アプリケーションが進化していく能力を制限します。PHPやフレームワークのアップデートを妨げたり、修正されない脆弱性を抱え込んだり、廃止された拡張機能に依存したり、他システムと適合しなくなったデータ形式を強いたりする可能性があります。問題は技術面だけではありません。脆弱なパッケージは一つ増えるごとに、プロダクトを変更するコストとリスクを増大させます。

PHPの保守されていないパッケージを置き換える際の目標は、リポジトリ全体を一度にモダナイズすることではありません。ビジネスに必要な振る舞いを維持しつつ、各ステップをロールバックできる状態を保ち、検証可能な形でリスクを低減することです。

保守終了を進化上のリスクとして扱う

保守終了を進化上のリスクとして扱う — guía visual de DedicatedPHP

本番環境で動作し続けていても、パッケージは保守されていない可能性があります。重要な兆候は最終変更日だけではなく、システムとともに変化できる能力です。セキュリティ修正を受け取っているか、現在のPHPバージョンとの互換性を宣言しているか、間接依存関係が固定されているか、チームがその内部の障害を診断できるかを評価するべきです。

どこに配置されているかも重要です。内部タスクで使われる書式設定ライブラリと、認証、決済、税務書類の生成、個人データの処理を担うコンポーネントでは、プロファイルが異なります。優先度は、障害の発生可能性、ビジネスへの影響、介入コストを組み合わせて決める必要があります。

古い依存関係のすべてが即時の置き換えを必要とするわけではありません。隔離されており、信頼できない入力を処理せず、振る舞いが安定していて、必要な変更を妨げないなら、カプセル化して廃止を計画することが合理的な場合があります。一方、インターネットに公開されているコンポーネント、または実行環境の更新を阻害するコンポーネントは、より早い判断を必要とします。

判断に使えるインベントリを作る

composer.jsoncomposer.lockの一覧は出発点であり、分析そのものではありません。有用なインベントリは直接依存関係と推移的依存関係の両方を特定し、運用上の問いに答えます。

  • 実際の利用:どのクラス、コマンド、コントローラー、プロセスがそのパッケージを呼び出し、どの頻度で使うか。
  • ビジネス機能:障害時に中断するフローが、アクセス、購入、請求、インポート、補助タスクのどれか。
  • 露出:ユーザー、プロバイダー、webhook、ファイル、内部ネットワークからのデータを受け取るか。
  • 結合度:型、例外、シリアライズされた構造、クエリがアプリケーション全体に散在しているか。
  • カバレッジ:現在の振る舞いを記述するテストは何か、またどの領域が手動でしか検証されていないか。
  • 制約:PHPバージョン、拡張機能、データベース、キュー、外部API、規制要件。

静的検索は参照箇所の特定に役立ちますが、システムの観測に代わるものではありません。非同期ジョブ、コンソールスクリプト、あまり使われないルート、設定によって有効になる統合、動的に読み込まれるコードを確認してください。周辺的に見える依存関係が、月次締めや運用復旧時には決定的となる場合があります。

更新、カプセル化、置き換え、削除を選ぶ

主な判断は四つあり、移行中に相互排他的ではありません。

  • 更新:インターフェースと要件を受け入れられる保守版が存在する場合に適します。破壊的変更、推移的依存関係、必要となるPHPバージョンの更新を確認してください。
  • カプセル化:現行パッケージの周囲に独自の境界を作ります。置き換えを判断する前に結合度を下げる必要がある場合、または代替案がまだ成熟していない場合に適しています。
  • 置き換え:コンポーネントを別のパッケージ、外部サービス、または必要なユースケースに限定した内部実装へ変更します。メソッド名の類似ではなく、明示的な契約に基づく必要があります。
  • 削除:もはや価値を提供しない、重複している、またはネイティブ機能で解決できる機能を取り除きます。将来の負荷が最も小さい選択肢であることが多い一方、隠れた利用者が存在しないことの確認が必要です。

人気がある、または互換性がありそうだという理由だけでライブラリを採用するのは避けてください。ライセンス、観測可能な保守状況、APIの表面積、エラーモデル、性能、形式サポート、セキュリティ戦略、ベンダー依存を比較してください。必要性が小さいなら、単純な内部抽象のほうが、別の大規模パッケージを導入するより安定する場合があります。

契約とテストで互換性を確認する

ドキュメントはAPIの意図を説明しますが、本番コードは本当に重要な契約を明らかにします。パッケージを変更する前に、現在のケースに対する特性化テストを構築してください。これは古い設計が理想的であることを示すためではなく、望ましくない変更を検出するために重要な結果を固定するものです。

境界データ、null値、エンコーディング、日付、小数精度、他のコンポーネントが利用するエラーメッセージを含め、入力と出力の例を定義してください。パッケージがドキュメント、イベント、APIレスポンスを生成する場合は、代表的なサンプルを保存し、その構造を検証してください。

警告なく壊れがちな側面

  • 永続化:欠損値とnull値の違い、トランザクション、生成される識別子、操作順序。
  • シリアライゼーション:フィールド名、タイムゾーン、日付形式、Unicode、数値型、後方互換性。
  • 統合:認証、リトライ、タイムアウト、署名、ページネーション、部分的なレスポンスの解釈。
  • エラー:例外、コード、記録可能なメッセージ、リトライまたは人手の介入を引き起こすべき条件。
  • 性能:メモリ消費、クエリ数、バッチサイズ、クリティカルパスにおけるレイテンシ。

ユニットテストは独自ロジックには有用ですが、統合が変わる場合には十分ではありません。データベースまたは管理された環境に対する統合テストと、外部システムとの境界における契約テストを追加してください。高影響度のプロセスでは、変更をユーザーに公開する前に、匿名化データまたは合成データで比較を実行してください。

置き換え前にアダプター層を設計する

アダプター層は、アプリケーションの契約を依存関係の契約へ変換します。コントローラー、サービス、キュージョブがライブラリを直接呼び出すことを許すのではなく、ビジネスニーズを中心としたインターフェースを定義してください。たとえば、ドキュメント変換サービスは、独自の操作を公開し、パッケージの内部型ではなくドメインオブジェクトを返すべきです。

interface DocumentRenderer
{
    public function render(Invoice $invoice): RenderedDocument;
}

現行実装はそのインターフェースの背後に置かれます。その後、新しいコンポーネントを使う二つ目の実装を組み込みます。これにより変更を一点に限定でき、比較テストが容易になり、置き換え固有の事情がコード全体へ広がることを防げます。

抽象は意図的に小さくするべきです。ライブラリのすべてのメソッドを複製するインターフェースは結合度を下げず、単に一層を追加するだけです。アプリケーションが現在必要とする操作をモデル化し、無効な入力に対して何が起こるか、どのデータを保持するか、サイズまたは時間の上限は何かといった重要な判断を文書化してください。

段階的かつ可逆的な移行を実行する

  1. 範囲を限定する:すべての利用箇所に手を加える前に、一つのフロー、一つのコンシューマー、または一つの操作を選択します。
  2. 振る舞いを特性化する:通常ケース、境界、障害を表すテストとサンプルを追加します。
  3. アダプターを導入する:まずは既存実装を新しい境界の背後に維持します。
  4. 代替を実装する:合意された契約を変更せずに、データとエラーを変換します。
  5. 結果を比較する:安全な場合は、両実装で同等の入力を処理し、重要な差異を記録します。
  6. コンシューマーを移行する:以前のパッケージへの直接参照がなくなるまで、フローを一つずつ変更します。
  7. 暫定コードを削除する:不要になった古い実装、フラグ、互換性パスを削除します。

段階的な有効化を使用する場合は、進行を決めるメトリクスとロールバックを強制するメトリクスを定義してください。設定フラグで実装を選択できますが、恒久的に二つの正規の情報源を併存させてはなりません。書き込みを伴う操作では、冪等性とリコンシリエーションが明示的に設計されていない限り、両方のパスが同じリソースを変更しないようにしてください。

明確な診断シグナルとともにデプロイする

デプロイは完全なリリースと同義ではありません。コードを公開することと、その振る舞いを全ユーザーに対して有効にすることは異なります。リスクが正当化する場合は、この二つの時点を分けてください。新実装を無効な状態でデプロイし、技術的な健全性を確認してから、アーキテクチャが許せば変更を限定的に有効化します。

開始前に、観測可能な指標について合意してください。操作ごとのエラー率、応答時間、リトライ、失敗したジョブ、出力差異、サポートインシデントの件数です。機密データを含めずに、問題を旧パスまたは新パスに帰属させられるよう、トレースとログに実装識別子を記録してください。

ロールバックはテスト済みで、移行中に生成されたデータと互換性がなければなりません。以前のコードへ戻すだけでは、不可逆なスキーマ変更、公開済みイベント、送信済みドキュメントを解決できません。そのようなケースでは、まず補償、加算的マイグレーション、または互換性ウィンドウを設計してください。

クリティカルな依存関係のチェックリスト

クリティカルな依存関係のチェックリスト — guía visual de DedicatedPHP
  • 実際の利用状況とビジネス上の重要度は文書化されているか。
  • 推移的依存関係とプラットフォーム制約を把握しているか。
  • パッケージ型の露出を避ける独自の契約があるか。
  • 特性化、統合、および重要なエラーに関するテストがあるか。
  • データ、シリアライゼーション、永続化、セキュリティ、性能を検証したか。
  • 有効化を限定でき、ロールバックはデータ変更を考慮しているか。
  • 互換性と一時コードを削除する日付と明示的な基準があるか。

安全な置き換えとは、新しいパッケージがコンパイルできることではありません。重要な結果を維持し、差異を可視化し、アプリケーションとともに進化できなくなったコンポーネントへの依存を恒久的に低減することです。

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