
Summary
この文書の要点
- 共通化する対象は完成した業務システムではなく、Platform、Shared Shell、Primitive、Domain Engine、API、契約です。顧客固有の画面と業務フローはClient Applicationに残します。
- CloudflareをData Plane、Google CloudをControl Planeとする考え方は出発点です。配置は製品名ではなく、データの正本、処理の重さ、共有範囲、障害時の振る舞いから決めます。
- 各データのSystem of Recordを一つに決め、同期Dual Writeを避けます。別クラウドに必要な複製は、正本からイベントで作るCache、Replica、Derived dataとして区別します。
- 境界API、短期資格情報、テナント文脈、冪等性、分散トレース、縮退運転を一つの設計として扱い、段階的かつ可逆に移行します。
コードの層分けより先に、クラウド間の所有者を決める
複数の顧客アプリを一つのコードベースから展開しようとすると、共通化の対象が増え続けます。認証、通知、監査のような横断機能だけでなく、画面、メニュー、入力項目、業務フローまで設定で吸収し始めると、共通部分が巨大な条件分岐になり、一つの変更が無関係な顧客アプリへ影響します。
この問題に対して4層モデルは、共通化する能力と個別実装する体験を分ける道具になります。しかし、アプリケーションコードだけを4層へ分類しても、CloudflareとGoogle Cloudの両方に同じデータや同じ責務が残れば、運用上の境界は曖昧なままです。障害対応、権限変更、バックアップ、削除、監査のたびに、どちらが正しい状態かを人が判断することになります。
そこで、すべてのCompute、Data、Secret、APIについてOwnerを一つ決めます。Cloudflareを顧客に近い実行面、Google Cloudを共有基盤の制御面とするのは有力な初期仮説ですが、例外を禁止する規則ではありません。データの正本、処理の場所、性能、セキュリティ、費用、運用体制を確認し、境界を越える理由を説明できる配置だけを採用します。
- 層はコードの依存方向を整理し、クラウド境界は所有権と運用責任を整理する。
- 共通化の量ではなく、変更理由と障害範囲が一致しているかで境界を評価する。
- 例外を認める場合も、Owner、System of Record、通信契約、復旧手順を曖昧にしない。
4層で、共通基盤から顧客固有UXまでを分離する
L0 Platformは、業務ドメインを知らなくても成立する共通基盤です。Identity、Tenant、RBAC、通知、課金、利用量、AI Gateway、監査、ジョブ、可観測性などが候補になります。ここへ個別の画面名や業務手順が入り始めたら、Platformではなく上位層の責務が漏れている可能性があります。
L1 Shared Shellは、顧客アプリを安全に起動する最小の枠です。認証連携、テナント文脈、権限ガード、API client、error boundary、design system、routing、accessibilityを提供します。同じL1に置くPrimitiveは、Money、Quantity、DateRange、Attachmentのような安定した基本概念です。複数箇所から参照されるという理由だけで、変化の多い業務モデルをPrimitiveへ移してはいけません。
L2 Domain Engineは、請求、在庫、予約のような業務能力を再利用可能な単位へ切り出す層です。副作用のない計算はlibrary、状態を持つ業務能力はDomain API、複数ドメインを横断する能力はPlatform Serviceとして提供します。L3 Client Applicationには、顧客固有の画面、導線、入力項目、メニュー、業務フローを置きます。不要な機能を権限やfeature flagで隠すのではなく、最初から組み込まない構成を基本にします。
- L0は業務を知らない共通基盤、L1はアプリ起動の枠と安定概念を担当する。
- L2は再利用可能な業務能力を、状態の有無と横断性に応じてlibrary、API、Platform Serviceへ分ける。
- L3は顧客ごとの差を明示的なコードとして持ち、巨大な設定機構へ押し込まない。
L0 Platform
Identity / Tenant / RBAC / Notification / Audit / Job
L1 Shared Shell / Primitives
Auth integration / API client / Design system / Money / DateRange
L2 Domain Engines / APIs
Pure logic library / Stateful Domain API
L3 Client Applications
Client-specific UI / UX / Workflow / Data製品名ではなく、Control PlaneとData Planeで配置を決める
Cloudflare側は、DNS、CDN、WAF、rate limiting、静的配信、Cloudflare Workersによる顧客アプリの入口を担当します。顧客単位で完結し、低遅延で扱いたいデータはD1、R2、KV、Durable Objectsの候補です。ただし、保存先は一括して決めず、整合性、transaction、容量、read/write pattern、保持期間、backup、削除要件をEntityごとに評価します。
Google Cloud側は、すべての顧客アプリから共有される中核能力を担当します。Tenant Registry、課金、利用量、中央監査、AI Gateway、共有Domain API、重いbatch、長時間job、全体ポリシーが候補です。Cloud Run、Cloud Run Jobs、Cloud Tasks、Pub/Sub、Secret Managerなどは、この責務を満たすために必要なものだけを選びます。Google Cloudにあるという理由だけで全サービスをCloud Runへ寄せる設計にはしません。
判断の中心はControl PlaneとData Planeです。契約、権限、利用可能な機能、全体ポリシーを制御する状態はControl Planeへ、実際の顧客業務、顧客固有文書、個別workflowを処理する状態はData Planeへ置きます。ただし、データの近くで処理するData Gravityも考慮します。D1が正本なら計算もCloudflare側、Google Cloudのデータが正本なら重い処理も同じ側へ置き、不要なクラウド往復を増やしません。
- Cloudflare候補: Edge、Client Application Runtime、顧客単位のData Plane、低遅延処理。
- Google Cloud候補: Platform、Control Plane、共有Domain Service、重処理、中央ガバナンス。
- CF → GCP → CF → GCPのような往復を避け、処理を正本データの近くへ置く。
System of Recordを一つに決め、同期Dual Writeを避ける
クラウド境界を安定させる最も重要な作業は、主要EntityごとにSystem of Recordを決めることです。Owner、正本、実行場所、backup、保持期間、削除責任を表にし、CloudflareとGoogle Cloudの両方に同じ正本が存在する状態をなくします。別の場所にデータを置く場合は、Authoritative、Replica、Cache、Derivedのどれかを明記します。
ブラウザからD1とGoogle Cloud側DBへ同時に書き込む同期Dual Writeは、一方だけ成功したときに整合性を失います。二つのtransactionをまたいだ完全な原子性を期待するより、一つのSoRへ書き込み、その結果をeventとして送信し、別側にprojectionを非同期生成する方が所有権を保ちやすくなります。
非同期化しても整合性の問題が消えるわけではありません。event_id、schema_version、tenant_id、occurred_at、correlation_idを持たせ、冪等性、重複配信、順序、retry、poison event、DLQ、再処理手順を設計します。ReplicaやDerived dataが遅れていることを画面と運用が許容できないなら、その処理は非同期化せず、境界自体を見直す必要があります。
Entity Owner System of Record Secondary copy
Tenant policy Platform Google Cloud Edge cache
Client workflow Client app Cloudflare D1 Audit projection
Client document Client app Cloudflare R2 Backup replica
Usage aggregate Platform Google Cloud Derived dashboardSystem of Record
-> Outbox / Event
-> Retryable delivery
-> Idempotent consumer
-> Replica / Cache / Derived projection境界APIを一つ置き、内部サービスと長期秘密鍵を外へ出さない
Cloudflare側のClient ApplicationがGoogle Cloud内部サービスを個別に知ると、service追加や分割のたびに境界契約が増えます。外部に見せる入口をPlatform Boundary APIへ集約し、Identity、通知、AI、課金、共有Domainの安定した契約を提供します。内部のCloud RunはPUBLIC、EDGE-ONLY、INTERNAL、PRIVATEに分類し、公開が必要な入口だけに到達経路を持たせます。
境界契約はOpenAPIやJSON Schemaで版管理し、request、response、error、pagination、idempotency key、correlation IDを定義します。特にtenant_idをbodyだけから信用せず、検証済みのmachine identityとユーザー文脈から認可対象を確定します。境界で認証、認可、rate limit、schema validationを行い、内部サービス名や例外詳細をそのまま返しません。
CloudflareからGoogle Cloudへの機械認証では、静的なService Account Keyを第一選択にしません。OIDCとWorkload Identity Federationなどによる短期資格情報を候補にし、token lifetime、audience、clock skew、replay protection、key rotation、交換時の遅延と運用負荷を測ります。短期資格情報の方式が要件に合わない場合も、別方式を採用した理由、秘密の保存先、更新手順、失効手順を設計記録へ残します。
- Client Applicationが内部Cloud RunのURLやservice構成へ直接依存しない。
- 境界APIは認証だけでなく、tenant context、認可、rate limit、schema、request IDを確定する。
- 長期鍵を配布する前に、短期資格情報の実現性、待ち時間、障害時の再取得、運用負荷を検証する。
即時応答、イベント、ジョブを利用者の待ち時間で選ぶ
境界を越える処理をすべて同期APIにすると、Google Cloud側の遅延や障害がCloudflare側の画面全体へ伝播します。一方、すべてをeventへ変えると、利用者が確定結果を必要とする操作まで結果整合性になり、再試行と状態表示が複雑になります。通信方式は技術の好みではなく、利用者がいつ結果を必要とするかで選びます。
権限確認や即時のAI応答など、その場で結果が必要な処理は同期APIの候補です。監査projection、外部通知、利用量集計のように結果を待たなくてよい処理はeventまたはqueueへ送ります。大きなファイル処理や長時間のAI処理はjobとして受け付け、job IDで進行状況を確認できる契約にします。
同期経路にはhopごとのtimeoutと全体のlatency budgetを置きます。retryは冪等な操作に限定し、書き込みではidempotency keyを使います。非同期経路は受理と完了を分け、queueへ入っただけで業務処理が完了したと表示しません。どの状態が利用者に見え、どの失敗を再実行できるかまでがAPI契約です。
Immediate result required -> Synchronous API
Result can arrive later -> Event / Queue
Heavy or long-running work -> Job + status API
External notification -> Event / Queue片側の障害を全停止へ広げず、機能ごとに縮退方法を決める
Google Cloud側が利用できないときに顧客アプリ全体が停止する構成は、共有基盤の障害範囲をすべてのData Planeへ広げます。Cloudflare側の正本だけで成立する閲覧や更新は継続し、期限内のcacheで安全に判断できる機能はcacheを利用します。権限や課金のように古い情報で進める方が危険な操作は、明示的に停止します。
各機能を、継続可能、read-only、queueへ退避、完全停止の四つに分類します。たとえば顧客ローカルの下書きは継続できても、中央のentitlement確認が必要な公開操作は停止する、といった違いを先に決めます。失敗時に200と古い結果を返すのか、202で受理するのか、503で停止するのかを曖昧にしません。
縮退運転には復帰手順も必要です。queueへ退避した更新は、回復後に順序と重複を処理し、利用者へ最終結果を示します。cacheには有効期限と更新元を持たせ、障害中に許容する古さを機能ごとに設定します。障害注入テストではGoogle Cloudへの通信を遮断し、主要な顧客ローカル機能、危険な書き込みの停止、復帰後の再処理を確認します。
Client-local operation -> Continue
Safe cached read -> Continue within TTL
Non-critical write -> Queue and show pending
Policy-dependent operation -> Fail explicitly
Critical cross-cloud write -> Stop; never pretend successテナント分離、監査、分散トレースを境界の共通仕様にする
マルチテナント構成では、アプリケーションのfilterだけに分離を任せません。tenant contextの発行元を一つに決め、境界APIで署名とaudienceを検証し、Google Cloud内部では専用Service AccountとLeast Privilegeを適用します。D1やR2を顧客単位に分けるか共有するかは、transactionだけでなく、権限、backup、削除、移行、障害範囲まで比較して決めます。
CloudflareとGoogle Cloudではログが分かれるため、browser、Worker、Boundary API、Cloud Run、queue、jobを一つの処理として追える識別子が必要です。trace_id、request_id、tenant_id、client_app、service、operation、correlation_idを伝播させます。ただし、個人情報や本文を識別子代わりにせず、必要なcontextだけを構造化して記録します。
Application LogとAudit Logは目的が異なります。前者は不具合と性能の診断に使い、後者はwho、when、tenant、action、resource、resultを追跡します。中央監査へeventを集める場合も、Data Planeの正本を書き換える経路にしません。監査側の障害で顧客操作を止めるか、outboxへ保持して後送するかを、規制と業務要件に合わせて決めます。
- tenant_idは利用者入力ではなく、検証済みの認証文脈から確定する。
- 専用Service AccountとIAMでEast-West通信を制限し、境界入口以外を不要にpublicへしない。
- Application Log、Distributed Trace、Audit Logを分け、同じcorrelation IDで関連付ける。
- AI処理へ送るデータは顧客DB全体ではなく、その処理に必要なcontextへ最小化する。
層とOwnerをデプロイ単位へ反映し、段階的に移行する
論理的に責務を分けても、すべてを同時にデプロイする構成では障害範囲が残ります。Platform API、Domain API、Shared Shell package、Client Application、schema、Infrastructure as Codeを独立した変更単位として整理し、互換性のある順序で公開します。環境名、project、domain、DB、queue、secretを同じ規則で対応付け、検証環境から本番の資源を参照できないようにします。
移行はBig Bang Rewriteにしません。最初に現在のCompute、Data、Secret、通信、デプロイ経路を棚卸しし、次にクラウド間の責務、System of Record、境界契約を確定します。その後でPlatformとShared Shellを抽出し、顧客固有機能とDomain Engine候補を分け、最後にデータ移行とインフラ分離を進めます。
各段階にはobjective、scope、data migration、API change、dependency、risk、security impact、cost impact、rollback、completion criteriaを持たせます。契約を先に互換化し、両経路を観測してからtrafficを切り替え、旧経路は復旧可能な期間を経て縮退します。完了条件は新しい構成が動くことだけでなく、旧経路へ戻せること、正本の件数と整合性が一致すること、境界を越えるtraceを追えることまで含めます。
- 棚卸し → 責務境界 → System of Record → 境界契約 → Platform/Shell抽出 → データ・インフラ移行の順で進める。
- APIとevent schemaは後方互換を保ち、producerとconsumerを同時更新しなくても移行できるようにする。
- Infrastructure as CodeのOwnerをresourceごとに一つにし、同じresourceを複数stackから管理しない。
- 切り替え、検証、rollback、旧経路削除を別の完了条件として記録する。
すべてを一つの方式へ寄せると、境界はかえって弱くなる
この設計は、すべてをmicroservice、Cloud Run、Cloudflare Workers、D1、API、event drivenへ変える提案ではありません。小さく安定したpure logicはlibraryの方が運用対象を増やさず、強いtransactionが必要な処理は一つのDBとserviceへ閉じた方が明確です。境界を増やすたびに、認証、契約、監視、retry、versioning、障害対応の費用が増えます。
顧客別DBは削除と障害分離を明確にする一方、migrationと監視の対象を増やします。共有DBは運用対象を減らしますが、filter漏れやresource競合の影響を広げます。Cloudflare Tunnelもprivate networkや管理経路には有力ですが、WorkersからCloud Runへの標準HTTPS通信へ無条件に追加すると、経路と運用を増やすだけになる場合があります。
評価の優先順位は、Security、Simplicity、Clear ownership、Data isolation、Failure isolation、Low coupling、Explicit contracts、Operational simplicity、Cost、Performance、Reusability、Future extensibilityです。特に、将来の再利用性を高めるために現在の所有権を曖昧にしてはいけません。共通化によって境界が説明しにくくなるなら、その共通化はまだ早いと判断します。
持ち帰るべき判断軸は、共通化の量ではなく所有権の明確さ
4層モデルでは、PlatformとShared Shellを共通化し、Domain capabilityをEngineまたはAPIとして再利用し、顧客固有のUXとworkflowをClient Applicationへ残します。この分け方により、基盤の改善を複数アプリへ届けながら、個別の業務変更を無関係なアプリへ波及させにくくなります。
クラウド境界では、Cloudflareを顧客に近いData Plane、Google Cloudを共有のControl Planeとして捉えます。ただし配置を決めるのは名称ではありません。System of Record、Data Gravity、通信頻度、必要な整合性、障害時の振る舞い、運用OwnerをEntityと処理ごとに確認します。
最終的に確かめるべきことは単純です。各Compute、Data、Secret、APIのOwnerが一つか、境界を越える理由と契約を説明できるか、片側が停止したときの状態を利用者へ正しく示せるか、そして段階的に戻せるか。この四点がそろって初めて、二つのクラウドを使う構成が、一つの保守可能なシステムとして成立します。


