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

ビジネスルールを重複させずにPHPレガシーシステムを段階的に置き換える方法

明確な境界、一貫したデータ、比較可能な結果、検証可能な廃止条件を定め、レガシーPHPアプリケーションの機能を移行する戦略を紹介します。

レガシーPHPアプリケーションと新システムの間で機能をルーティングする段階的な移行の概念図

レガシーPHPアプリケーションを置き換えるために、一度に全面書き換えを行う必要はありません。段階的な置き換えなら、ビジネスを継続しながら機能を移行できます。ただし、各操作をどのシステムが処理し、各データをどのシステムが管理するのかを正確に決めておく必要があります。境界が曖昧だと、2つのアプリケーションが異なるルールを実行し、互換性のない書き込みを受け付け、照合しにくい結果を生むおそれがあります。

目標は、2つの実装を無期限に並行稼働させることではありません。責任を制御しながら移し、新しいソリューションが必要な動作を維持していることを確認し、十分な証拠がそろった段階で古い依存関係を廃止することです。そのためには、機能上の境界、可観測性、データドメインごとの信頼できる唯一の情報源、そして技術部門とプロダクト部門が合意した進行基準が必要です。

2つのアプリケーションを並行稼働させるだけでは不十分な理由

2つのアプリケーションを並行稼働させるだけでは不十分な理由 — guía visual de DedicatedPHP

旧アプリケーションと新アプリケーションの両方を利用可能にしても、それだけで移行が定義されるわけではありません。両方が同じ操作を受け付けると、バリデーション、権限、価格、状態、副作用に異なるルールが適用される可能性があります。問題が表面化するのはたいてい後になってからです。請求額が異なる方法で計算されたり、通知が二重に送信されたり、状態変更がすべてのシステムに反映されなかったりします。

PHPのレガシーシステムを段階的に置き換えるには、どの機能を移行するのか、どのシステムがリクエストを受け取るのか、データに対する権限をどこに持たせるのかを明示する必要があります。境界は、コントローラーやテーブルの単なる一覧ではなく、機能に基づくものでなければなりません。たとえば「返品を処理する」という機能には、承認、在庫更新、返金が含まれることがあります。こうした依存関係を理解せずに分割すると、実態に合わない境界を作ることになります。

ルーティングを変更する前に、入力、出力、重要なエラー、権限、副作用、外部依存関係など、観測可能な動作を記録してください。ビジネスルールと、実装上たまたまそうなっているだけの詳細は区別します。過去の動作をすべて維持する必要はありませんが、意図的な変更は移行の影響として黙って扱うのではなく、変更として承認し、テストする必要があります。

検証可能な最初の機能を選ぶ

最初の候補は、最小の機能や最も目立つ機能である必要はありません。境界が理解しやすく、結合度が適度で、結果を検証できる機能を優先するとよいでしょう。運用上の価値も重要です。移行によってリスクを減らせる、頻繁な変更を可能にできる、あるいはビジネスが進化させる必要のある領域を改善できる場合があります。

  • 結合度:他モジュールへの呼び出し、共有テーブルへの直接アクセス、スケジュールされたタスク、グローバル状態への依存関係を洗い出します。依存関係が多くても移行は可能ですが、境界の定義にかかるコストは上がります。
  • 検証可能性:代表的なケースを定義でき、主観的な解釈に頼らず結果を観測できるか確認します。
  • 影響と可逆性:機能が失敗した場合の影響と、データを失ったり外部への副作用を重複させたりせずに以前の経路へ戻せるかを評価します。
  • 運用準備:実トラフィックに公開する前に、不整合を調査する担当者、メトリクス、手順が整っていることを確認します。

簡潔な一覧を作ると役立ちます。機能、利用者、読み書きするデータ、外部への副作用、プロダクト担当者、技術担当者を記録してください。ある操作の利用者や、あるデータを更新するシステムが分からない場合、まずシステムへの理解を深める必要があります。すぐに移行しても、不確実性は減りません。

ルーティングとデータの権限を定義する

ルーターは、どの実装がリクエストを処理するかを明示的に決定する必要があります。アーキテクチャに応じて、アプリケーション層、ゲートウェイ、または入口での判断が考えられます。重要なのは、機能、クライアント、操作、または管理されたコホートなど、判断基準が観測可能で一貫していることです。調整なしに同じ注文を2つの経路で処理できる状態は避けてください。

各境界で、データまたは集約ごとに所有者を割り当てます。たとえば新システムが注文の所有者なら、旧システムが共有テーブルへのアクセスを通じて注文を直接変更し続けるべきではありません。整合性要件に応じて、他のコンポーネントは定義済みのインターフェースを介して参照するか、イベントで変更を受け取ることができます。データベースの共有は移行中の段階としてはあり得ますが、権限の共有を意味するものではありません。

同期では、その方向と目的を明確にしてください。読み取り用のコピーは、レポートや旧システムの利用者にデータを提供できますが、誤って2つ目の書き込み元として権限を持たせてはいけません。イベントを交換する場合は、識別子、必要に応じた順序、再試行、重複排除、失敗したメッセージの処理方法を定義します。遅延をどう検知し、誰が不一致を解決するのかも明確にしてください。

曖昧な二重書き込みを避ける

リクエスト中に2つのシステムへ書き込む方法は、両者を簡単に同期できるように見えますが、部分的な失敗を招きます。一方の書き込みだけが完了することがあり、リクエストを再試行すると操作が重複する可能性もあります。冪等性、照合、エラーへの対策がなければ、独立したアプリケーション間の整合性を保証する単一のトランザクションは存在しません。

各操作について、書き込み元を1つにすることを優先してください。移行中は、所有者であるシステムが変更を公開し、読み取り用のプロジェクションや互換性を保つためのコピーを更新できます。非同期処理では、インフラが対応している場合、変更とメッセージを同じトランザクション境界で確実に記録し、コンシューマーを冪等に設計します。特定の制約により二重書き込みが必要な場合は、一時的な例外として扱い、順序、起こり得る障害、照合方法、終了日または終了条件を明記してください。

過去データのコピーにもルールが必要です。初期状態をどう取り込み、同時進行の変更をどう適用し、所有者を切り替える前に結果をどう検証するかを定義してください。インポートが成功したからといって、関連データ、派生状態、権限まですべてそろっているとは限りません。

副作用を重複させずに動作を比較する

比較によってリスクを減らすには、計算と実行を区別します。たとえば、入力のコピーを新しい実装に送り、監視モードで実行して、応答を稼働中のシステムと比較できます。その際、コピー側からメール送信、課金、在庫変更、その他の外部への副作用を実行させてはいけません。この実行では、管理されたデータや依存関係を使うか、副作用を意図の記録だけにとどめるアダプターに置き換える必要があります。

比較するのは意味上の結果です。フィールドの順序やタイムスタンプが必ず一致する必要はなく、状態、金額、判断、重要なエラーを比較します。境界値、不完全なデータ、権限、依存関係の障害を含む代表的なケースを使ってください。重要なルールには自動テストを追加し、実データが評価に適さない場合は、匿名化した例や合成データを保持します。

シャドー実行だけでは、新システムが実運用できることを証明できません。レイテンシ、並行処理、実際の統合を再現できない場合があり、差異は欠陥だけでなく、文書化されていない旧ルールを示している可能性もあります。各差異を分類し、担当者を割り当てて解決してください。修正する、承認済みの変更として受け入れる、または検証可能な理由を付けて対象外とする、のいずれかです。

進行状況を計測し、切り戻しに備える

選択された経路、実装のバージョン、相関ID、各操作の結果を記録します。エラー、レイテンシ、再試行、同期遅延、不一致を機能ごとに分けて計測してください。不要な秘密情報や個人データを記録しないようにし、ログへのアクセス制御と保持期間を定めます。

公開範囲を広げる前に、停止基準と、誰が進行を止められるかを合意します。切り戻しは必ずしもルーティングを戻すだけでは済みません。新システムがすでに書き込みを受け付けている場合、旧アプリケーションに戻すと、最新の状態が旧側にない可能性があります。実行中の操作、保留中のメッセージ、書き込み済みのデータをどう扱うか手順に明記し、適切な環境で演習してください。可逆性を左右するのは設定項目だけではなく、状態と副作用です。

検証可能な条件に基づいて旧システムを廃止する

検証可能な条件に基づいて旧システムを廃止する — guía visual de DedicatedPHP

廃止の判断は、単独の日付ではなく証拠に基づくべきです。機能ごとに、予定したすべての経路が新しい所有者に到達していること、重要な操作とそのエラーケースが網羅されていること、コンシューマーやスケジュールされたジョブが旧コンポーネントに依存しなくなっていること、担当者のいない直接書き込みや照合作業が残っていないことを確認します。

その後、プロダクト部門と運用部門が合意した期間、対象機能に関係する負荷やサイクルに注意しながら稼働状況を観察します。コンポーネントを削除する前に、未処理データ、互換性を保つためのコピー、監査や保存の要件を確認してください。ドキュメント、アラート、サポート手順も更新します。「念のため」に古いコンポーネントを残すことにもコストがあります。権限や脆弱性、所有権を曖昧にする書き込み経路が残る可能性があります。

実践的な手順は次のとおりです。機能の境界を定める、データの権限を割り当てる、動作を文書化する、テストと可観測性を整える、データを取り込むか同期する、副作用なしで比較する、管理された範囲に公開する、結果を確認する、明示的な承認を得て依存関係を廃止する。いずれかの段階に受け入れ基準または担当者がいないなら、移行を次の段階へ進める準備はまだできていません。

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