営業上の要望は、明示的なプロダクト判断でなくif ($tenantId === ...)として実装されるようになると危険です。当初は緊急課題を解決します。しかし時間が経つと、その条件はコントローラ、テンプレート、キュープロセス、エクスポート、APIに現れます。その結果は設定ではありません。テスト、説明、廃止が困難な、暗黙的なプロダクトのバリアントです。
PHP SaaSの顧客別設定は、意図的かつ統制された差異を可能にすべきであり、過去の例外をすべて温存すべきではありません。有用な問いは「この顧客向けに実現できるか」ではなく、「この差異は、他の顧客も必要としうる、ルールと持続可能なサポートを備えたプロダクトの安定した次元を表すか」です。
警告サイン:恒久的なコード例外

体験を適応させることと、プロダクトの隠れた分岐を維持することには違いがあります。個別の要望が次のいずれかのサインを生む前に介入することが重要です。
- tenant、ドメイン、または顧客の識別子がビジネスロジックに現れる。
- 同じルールがUI、API、非同期ワーカーに複製されている。
- どの顧客に例外があり、誰が承認したかをチームが回答できない。
- プラン変更により、中央で定義されていない機能上の振る舞いが変わる。
- 適応を廃止するには、複数のリポジトリまたはサービスにある条件分岐を探す必要がある。
例外はディスカバリーや移行の間は正当でありえますが、オーナー、レビュー日、そして出口が必要です。すなわち、プロダクト機能にする、特定の統合として分離する、または拒否する、のいずれかです。分類せずに放置すると、技術的負債が文書化されていない営業上の約束に変わります。
設定、権限、ケイパビリティ、個別開発を混同しない
これらの仕組みは異なる問いに答えます。混同すると、不透明な設計と矛盾するルールが生じます。
- 設定:既存機能がtenantごとにどのように振る舞うかを定義します。たとえば、番号付けの形式、デフォルト言語、またはフローで追加承認が必要かどうかです。
- 権限:tenant内でアイデンティティが何を実行できるかを決定します。承認が必須に設定されていても、ユーザーは支払いを承認する権限を持てます。
- ケイパビリティ:tenantが機能または運用上の上限にアクセスできるかを示します。契約、プラン、段階的有効化に依存しえますが、ドメインロジック全体を含めるべきではありません。
- 個別開発:顧客固有システムとの統合や単一の契約上の変換のように、再利用可能なプロダクト次元に収まらない振る舞いを扱います。
実用的なルールが判断を助けます。アクションを実行する誰が変わるなら権限を使います。機能が存在するか、利用可能かが変わるならケイパビリティを使います。利用可能な機能がどのように動作するかが変わるなら設定を使います。ビジネスモデルが排他的に変わるなら、それをフラグに見せかけないでください。
設定可能にすべきものとコアに残すべきもの
明確な意味論、有限の値集合、既知のバリデーション、妥当な再利用の見込みがある場合、オプションは設定カタログに加える価値があります。また、理解可能なサポート体験も必要です。コードを調べずに、変更の効果を誰かが説明できなければなりません。
表示パラメータ、通知ポリシー、しきい値、承認シーケンス、地域設定、すでにサポートされているフロー間の選択は、一般に適した候補です。一方、セキュリティ不変条件、データ整合性、基礎となる財務計算、変更時に既存のエンティティまたは契約を再解釈する必要があるルールは、コアに残すべきです。
柔軟性だけを理由に任意のデータを設定にしないでください。スキーマのないJSONフィールドは、発見不可能な依存関係を隠しかねません。オプションが重要なルールを変更する場合は、型、許可値、使用条件、既存データへの影響を定義してください。
統制された設定モデルを構築する
単独のキーでは不十分です。カタログの各定義には、プロダクトを安全に運用できるメタデータを含める必要があります。
- キーと機能上の説明:実装詳細ではなくドメイン指向の、安定した名前。
- オーナー:その進化と廃止を決定するチームまたは責任者。
- スコープ:グローバル、tenant、組織単位、プロジェクト、またはユーザー。デフォルトで全スコープを許可しないでください。
- デフォルト値:オーバーライドがない場合の明示的な振る舞い。
- 型とバリデーション:真偽値、列挙、範囲付き数値、またはスキーマで検証された構造。
- 依存関係:他のオプション、ケイパビリティ、または移行状態に対する要件。
- 機密性:データ分類、および読み取りと変更のアクセスルール。
- ライフサイクル:該当する場合の導入日、レビュー、非推奨化、予定された廃止。
PHPでは、たとえばTenantSettingsのようなドメインサービスに解決を集約し、契約のない配列ではなく型付きオブジェクトを返します。アプリケーションは、文書化された優先順位に従ってグローバル値、tenant値、より特化した値を組み合わせられます。値がない場合は、各コンシューマーで異なる解釈をするのではなく、必ずデフォルト値に解決しなければなりません。
$policy = $tenantSettings->approvalPolicy($tenantId);
if ($policy->requiresSecondApproval()) {
$workflow->requestSecondApproval($order);
}
ストレージはリレーショナルでもドキュメント型でもかまいませんが、カタログとバリデーションは永続化形式に依存すべきではありません。また、変更の不変履歴を保持してください。変更前後の値、実行者、時点、理由、変更チャネルです。履歴はビジネスアクションの監査ログに代わるものではありませんが、どの設定が有効だったかを再構築できます。
適切な境界で判断を評価する
散在する条件分岐の問題は、それらをすべてコントローラへ移しても解決しません。ビジネスルールに影響する設定は、そのルールを適用するドメインサービスまたはポリシーで評価すべきです。コントローラはリクエストを変換し、テンプレートは結果を表示します。いずれもtenantポリシーを独自に決定すべきではありません。
複雑な振る舞いには、真偽値の連鎖ではなく、登録されたストラテジーまたはポリシーを使用してください。請求ポリシーは、tenantに必要なケイパビリティがあることを検証した後、サポートされるモードから実装を選択できます。これにより、UI、API、キューは同じ判断を呼び出します。
テンプレートは、アクションの表示・非表示に使うケイパビリティインジケータを含め、すでに準備されたビューを受け取れます。ボタンを隠すことは認可ではありません。UIが操作を公開していなくても、APIはサーバー側で権限、ケイパビリティ、設定を適用しなければなりません。
プランを硬直化させないケイパビリティと上限
商用プランはケイパビリティを付与できますが、if ($plan === '...')の集まりにすべきではありません。advanced_approvalsやapi_accessのような安定したケイパビリティをモデル化し、契約上または管理上のソースを通じて、どのtenantがそれを持つかを解決します。その後、機能ロジックはプラン名ではなくケイパビリティを参照します。
上限には、さらに正確な定義が必要です。何を数えるのか、どの時間枠で数えるのか、いつブロックを適用するのか、リトライとキュープロセスがどのように振る舞うのかを定義します。上限はすべてのエントリポイントで観測可能かつ一貫していなければなりません。統合がメインUIの外でリソースを作成する場合も、同じ制御を回避できません。
設定を安全かつ可逆的に変更する
オプションの変更は、進行中のジョブ、既存レコード、統合に即時の影響を与えることがあります。保存前に、型、管理権限、依存関係、現在の状態との互換性を検証してください。影響が重要な場合は、変更のプレビューを提供します。どのフローが有効になるか、どの制約に違反するか、どの将来の操作に影響するかです。
段階的有効化は、オプションをUI全体で公開することとは異なります。制御されたtenant群にケイパビリティを有効化し、一般公開する前にその振る舞いを観測できます。また、ロールバックも定義してください。どの値が以前の状態を復元するか、関連するデータ移行があるか、新しい設定の下で開始された操作がどうなるかです。
UI上で可逆な変更でも、データでは可逆でない場合があります。新しいポリシーを有効化する前に、両方の次元を別々に扱ってください。
キュー、API、統合で一貫性を保つ
非同期プロセスでは追加の判断が生じます。ジョブ実行時に設定を解決するのか、作成時にスナップショットを保持するのかです。現行ポリシーを尊重すべきアクションでは、実行時に解決し、ジョブのコンテキストにtenantを含めます。元の判断を再現すべきドキュメント、計算、通信では、コマンドとともに明示的なバージョンまたはスナップショットを保存します。
宣言せずに両方の選択肢を混在させないでください。更新済みの設定を参照すると、リトライの結果が変わることがあります。冪等性、設定バージョン、リトライ時に期待する振る舞いを定義してください。外部統合にも同等の契約が必要です。事前検証、エラー処理、上限、tenantごとのトレーサビリティを備え、シークレットや個人データを診断ログに送らないようにします。
監査とサポート:観測された振る舞いを説明する

サポートは、顧客がどのキー値を持つかだけでなく、なぜそのフローが表示されるのかに答える必要があります。tenant識別子、定義バージョン、有効値のソース(デフォルトまたはオーバーライド)、関連するケイパビリティ、評価結果を含む判断トレースを記録してください。この情報へのアクセスを制限し、機密値はマスキングします。
このトレーサビリティを、オプションごとの利用メトリクス、バリデーションエラー、変更失敗、未使用オプションで補完してください。未使用の設定は古くなっている可能性があり、長期間にわたり単一tenantだけが使用する設定はプロダクトレビューに値します。目的はすべての差異をなくすことではなく、各差異を明示的、検証可能、観測可能にし、価値を提供しなくなったときに廃止することです。



