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

PHP SaaSの変更承認フローを設計する方法

PHP SaaSで承認が必要な変更を見極め、状態と権限をモデル化し、監査と同時実行制御のもとで適用するための実践ガイドです。

SaaSアプリケーションにおけるデータ変更承認フローの図

設定や業務データの変更がほかのユーザーに影響する場合、反映前に、ある人が変更を提案し、別の人が審査する仕組みが必要になることがあります。PHP SaaSの変更承認フローを設計するには、「承認」ボタンを追加するだけでは不十分です。提案と現在のデータを分離し、各状態遷移を保護するとともに、申請の待機中に状況が変わった場合の対応を定める必要があります。

目標は、何が申請され、誰がどの権限で審査し、実際に適用されたかを、検証可能な形でシステムが示せることです。解決策はリスクに見合ったものでなければなりません。不必要なフローは手間を増やし、不十分なフローは誤った変更や追跡困難な変更を許すおそれがあります。

承認が必要な変更を見極める

承認が必要な変更を見極める — guía visual de DedicatedPHP

まず、影響度、範囲、修正のしやすさに基づいて変更を分類します。編集可能なデータすべてに審査が必要なわけではありません。影響の小さい個人設定なら、ユーザーがそのまま保存してもよいでしょう。一方、請求ルール、組織内で共有される設定、複数のプロセスに影響するマスターデータの変更には、承認を必須にするのが妥当な場合があります。

プロダクト上の基準を明確に定めます。たとえば、変更が提案者以外のユーザーにも影響するか、金額や権限を変えうるか、社内規定で別の担当者による確認が求められているか、修正にどれほどのコストがかかるかを確認します。例外と、それを変更できる担当者も文書化してください。対象と無関係な操作にまで承認を一般化するのは避けましょう。ここで扱うのは設定や業務データの変更です。

この判断は画面だけでなく、サーバー側にも反映させる必要があります。ボタンを隠しても、エンドポイントへの直接呼び出しは防げません。APIは変更の種類に応じて承認の要否を判定し、個別操作だけでなく、一括操作や同じデータを変更できる連携処理にもルールを適用しなければなりません。

申請と現在のデータを分離する

提案を作成するときに、現在のデータを上書きしてはいけません。少なくとも、組織、対象リソース、提案値、状態、作成者、作成日時を含む独立した申請として保存します。プロダクトによっては、理由、申請元、有効期限、元データのバージョンも保存する必要があります。

値の表現には、型付きフィールドを使う方法と、サーバーで検証したシリアライズ済み構造を保存する方法があります。どちらの場合も、任意のペイロードを直接更新として受け入れないでください。提案を受け取る際に、スキーマ、許可フィールド、形式、業務ルールを検証します。待機中に条件が変わることがあるため、適用時にも必要な検証を再度行います。

この設計なら、ほかのユーザーの利用体験に影響を与えずに、現在値と提案値を比較できます。また、審査画面に差分をわかりやすく表示できます。保留中の値を適用済みの値として表示してはいけません。適用済みのデータと提案内容を明確に区別してください。

状態と遷移を明示的にモデル化する

意味が明確な、少数の状態を定義します。一般的な状態遷移には、proposed(提案済み)、in_review(審査中)、approved(承認済み)、rejected(却下)、expired(期限切れ)、applied(適用済み)などがあります。審査状態が不要なプロダクトもあります。即時に判断できるなら、省略してもかまいません。重要なのは、各状態が運用上の状況を表し、単なる飾りのラベルにならないことです。

有効な状態遷移を明確にします。たとえば、提案は審査中に移行するか、却下されます。審査中の申請は承認または却下され、承認済みの申請は有効性が保たれていれば適用できます。期限切れの申請を、通常の有効な申請と同じ経路で承認してはいけません。画面に選択肢が表示されなくても、許可されていない操作には一貫した応答で失敗させます。

「承認済み」と「適用済み」は同じではありません。承認は判断を記録し、適用は現在のデータを変更します。両方の段階を維持すれば、適用時の失敗を検出し、対応が必要な承認済み申請を表示できます。短い単一のトランザクション内で処理を完了できる場合は、判断と書き込みを連携させてもかまいませんが、モデル上は何が起きたかを引き続き区別する必要があります。

各状態遷移で権限を検証する

組織、ロール、リソース、現在の状態に基づき、サーバー側で各操作を認可します。申請を作成できたからといって、承認や適用の権限もあると想定してはいけません。また、クライアントから送信された組織IDを信頼せず、検証済みのセッションまたは認証情報から対象範囲を特定し、申請とリソースが同じ範囲に属することを確認します。

自己承認を禁止する場合は、承認者と提案者のIDが異なることを確認します。このルールは画面だけでなく、状態遷移時に実行しなければなりません。管理者、サービスユーザー、自動処理による申請をどう扱うかも定めてください。例外は意図的なポリシーとして設けるべきであり、偶発的な抜け漏れであってはなりません。

承認は、特定の提案にひも付ける必要があります。内容が変わった場合、以前の承認は無効になります。申請の新しいバージョンを作成するか、再審査が必要な状態に戻してください。承認後に値を黙って変更すると、審査された内容と適用された内容の対応関係が崩れます。

同時実行、期限切れ、適用を管理する

申請の待機中に、別の担当者が元のデータを変更することがあります。この状況を検出するため、提案の作成時にリソースのバージョンまたは関連する値を記録します。適用時には、想定したバージョンと現在のバージョンを比較してください。一致しない場合、判断基準なしに新しい変更を上書きしてはいけません。プロダクトのルールに応じて、適用を止める、競合として記録する、再審査を求めるなどの対応を取ります。

リレーショナルデータベースでは、トランザクションによって申請の読み取り、状態の確認、リソースの更新を保護できます。具体的なパターンはデータベースエンジンと同時実行のレベルによって異なり、悲観ロックやバージョンを条件とした更新が適切な場合があります。いずれの場合も、状態遷移はアトミックでなければなりません。2件の申請が同時に、同じ変更を二重に適用してはいけません。

適用処理は冪等にするか、再試行を安全に拒否できるようにします。データベースの更新に加えて通知の送信やイベントの公開を行う場合は、保留中のイベントを同じトランザクション内に記録し、後から処理する方法を検討してください。これにより、書き込みが確定していないのに成功通知が表示されるリスクを低減できます。

申請を有効な状態に保てる期間、期限切れとなる条件、延長の可否など、期限切れのポリシーも定めます。承認時と適用時にはサーバーで期限を確認してください。スケジュール済みタスクで状態を変更するだけでは不十分です。遅延によって値や条件の信頼性が失われた場合は、新しい提案を求めます。

フローを監査・運用する

各変更の提案者、承認者、却下者、適用者について、日時、組織IDとリソースID、結果、調査に必要なコンテキストとともに記録を残します。提案のバージョンと判断への参照も保存してください。監査記録自体は、通常の申請編集機能から変更できないようにします。記録するデータは必要なものに限定し、プロダクトのアクセスおよび保持ポリシーを適用します。

運用画面では、保留中、期限切れ、競合により停止中、承認済みだが未適用の申請を見つけやすくします。停止理由を説明し、安全に解決する手段を用意してください。適用エラーを自動承認に置き換えてはいけません。通知は新しいタスクや判断を知らせるために使えますが、あくまで補助機能です。永続化され、照会できる状態を正とします。

不変条件、権限、障害をテストする

不変条件、権限、障害をテストする — guía visual de DedicatedPHP

決して破ってはならないルールをテストで確認します。未承認の申請が現在のデータを変更しないこと、組織へのアクセス権がない人が申請を読み取ったり状態遷移させたりできないこと、禁止されている場合に自己承認が失敗すること、承認済みの申請を審査内容と異なる値で適用できないことを検証します。終端状態、期限切れの申請、無効な状態遷移のテストも追加します。

2件の承認が競合する場合や、承認中に元データが編集される場合の競合状態もテストします。適用中の障害をシミュレーションし、書き込みが確定していないのに申請が適用済みとしてマークされないことを確認してください。申請を期限切れにするタスクがある場合は、繰り返し実行しても安全であることを検証します。こうしたテストによって業務ルールを検証可能な保証にでき、権限やデータモデルの変更後もフローを維持しやすくなります。

設計基準として、各段階に責任者、検証可能な条件、観測可能な結果を設けます。誰が各状態遷移を実行できるのか、同時変更が起きた場合に何が起きるのか、判断をどう再現するのかをチームが説明できないなら、本番導入の前にフローをさらに定義する必要があります。

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