システム全体像 System architecture

前提: Overview: モバイルアプリの唯一のサーバサイド接続先は Cloudflare Workers BFF (/v1/mobile/*) です。 Supabase DB / Edge Functions は叩きません。例外は Supabase Auth(JWT 取得のみ)。 画面・操作・テレメトリはそれぞれ /v1/mobile/views/home-feed などの views 系(ViewEnvelope)、 /v1/mobile/actions/sessions/start などの actions 系(ActionEnvelope)、/v1/mobile/telemetry/events(TelemetryAck)に集約されます。 The mobile app's single server-side surface is the Cloudflare Workers BFF (/v1/mobile/*). It does not hit Supabase DB or Edge Functions directly. The only exception is Supabase Auth (JWT acquisition only). Screens, actions, and telemetry are funnelled into views endpoints like /v1/mobile/views/home-feed (ViewEnvelope), actions endpoints like /v1/mobile/actions/sessions/start (ActionEnvelope), and /v1/mobile/telemetry/events (TelemetryAck).

1. 接続先は BFF だけ 1. Only the BFF

Flutter クライアント(mobileapp/prototype/flutter/)から直接叩ける外部サーフェスは次の 2 種類だけです。

The Flutter client (mobileapp/prototype/flutter/) is allowed to hit only two external surfaces.

接続先Surface 用途Purpose SDK / 方式SDK / how
Cloudflare Workers BFF
/v1/mobile/*
画面取得・操作・テレメトリ。全てのデータ・ビジネス判定はここを経由するFetch screens, run actions, push telemetry. All data and business decisions flow through here dio / http(生成された Dart クライアント)dio / http (generated Dart client)
Supabase Auth サインイン、OTP、JWT 取得・更新、パスワード変更・退会Sign-in, OTP, JWT acquisition / refresh, password change, delete account supabase_flutter SDK
(Auth 機能のみAuth features only)
禁止事項: Prohibited:
  • Supabase DB の直接 SELECT / INSERT / UPDATE / DELETE(supabase.from('parking_lots') 等)
  • Direct Supabase DB queries (supabase.from('parking_lots') etc.)
  • Supabase RPC 呼び出し(supabase.rpc('nearby_parking_lots') 等)
  • Supabase RPC calls (supabase.rpc('nearby_parking_lots') etc.)
  • Supabase Edge Functions 直接呼び出し
  • Direct calls to Supabase Edge Functions
  • Supabase Storage 直接 PUT(R2 upload も BFF で発行した presigned URL 経由)
  • Direct Supabase Storage PUTs (R2 uploads must use a BFF-issued presigned URL)
  • Supabase Realtime の購読(必要なら BFF 側 push / poll に寄せる)
  • Supabase Realtime subscriptions (shift to BFF-driven push or poll when needed)

2. 責任分界 2. Responsibility split

BFF は data + validation + navigation + UI Config を ViewEnvelope で返します。 UI コンポーネント構造はサーバーから返しません(描画は Flutter ネイティブ)。 ViewEnvelope の詳細契約は API レスポンス構造 が正です。

The BFF returns data + validation + navigation + UI Config inside a ViewEnvelope. It does not return UI component trees (rendering stays Flutter-native). See API response structure for the authoritative ViewEnvelope contract.

責任分界表 Responsibility matrix

責任Responsibility BFF(サーバー)BFF (server) Flutter(クライアント)Flutter (client)
データ shape(JSON 形状・fields)Data shape (JSON structure, field names) 決定decides 受信のみreceive only
ビジネスルール計算(料金・予約可否・premium判定)Business rule computation (pricing, eligibility, premium checks)
画面遷移先(次にどこへ行くか)Next screen / navigation target navigation 遵守follows
バリデーションルール(maxLength / pattern / required 等)Validation rules (maxLength / pattern / required) validation 即時 UI 反映applied in the UI in realtime
状態遷移(loading / empty / error / offline)State transitions (loading / empty / error / offline) states / fallback_behavior 枠組みに沿ってレンダーrenders within the frame
UI Config(文言・テーマ指示・feature flag)UI Config (copy, theme hints, feature flags) ui_config 受けて反映receives and applies
UI カラー / spacing / typography / アニメーションUI color / spacing / typography / animation 完全制御fully owned
UI コンポーネント構造UI component structure (L5 はやらない)(no L5) 完全に Flutterfully Flutter
ジェスチャー・マップ操作Gestures and map interactions
ローカル状態(入力途中・楽観的更新)Local state (in-progress forms, optimistic updates)
ローカル永続化(Isar / Secure Storage)Local persistence (Isar / Secure Storage) Flutter 責任Flutter's responsibility
Supabase Auth トークン管理(JWT / refresh)Supabase Auth token management (JWT / refresh) Flutter 責任Flutter's responsibility
分析テレメトリ送信Analytics telemetry endpoint を提供exposes endpoints Flutter がイベント発火Flutter fires events
Push 通知配信(FCM v1)Push notification delivery (FCM v1) BFF が FCM に送信BFF sends via FCM Flutter が受信・表示Flutter receives and renders
デバイストークン登録Device token registration 受け口accepts Flutter が firebase_messaging から取得Flutter pulls via firebase_messaging
画像配信Image delivery R2 / Cloudflare Image ResizingR2 / Cloudflare Image Resizing cached_network_image で取得fetched via cached_network_image

3. API サーフェスの 3 種類 3. Three API surfaces

モバイルが叩ける /v1/mobile/* の endpoint は以下の 3 種類。Resource API(/v1/parking-lots など)はモバイルからは叩きません。

Only three kinds of /v1/mobile/* endpoints exist. The generic resource API (/v1/parking-lots etc.) is off-limits for the mobile app.

種類Kind Path 返却Return 用途Purpose
View GET /v1/mobile/views/home-feed ViewEnvelope 画面入口。data + ui_config + navigation + validation + states + fallback_behavior + meta を返すScreen entry point. Returns data + ui_config + navigation + validation + states + fallback_behavior + meta
Action POST /v1/mobile/actions/sessions/start ActionEnvelope ユーザー操作の mutation。結果 + 遷移指示 + toast 文言を返すUser-initiated mutations. Returns result, navigation hint, and toast copy
Telemetry POST /v1/mobile/telemetry/events TelemetryAck fire-and-forget 記録(geofence / push 受信 / 位置情報等)Fire-and-forget records (geofence / push receipt / location samples)

ViewEnvelope の構造(要約) ViewEnvelope structure (summary)

{
  "data": { /* 画面固有の domain データ *//* screen-specific domain data */ },
  "ui_config": { /* 文言・theme hint・feature flag *//* copy, theme hint, feature flags */ },
  "navigation": { /* 次遷移先 / deep link target *//* next screen / deep link target */ },
  "validation": { /* フォーム validation ルール *//* form validation rules */ },
  "states": { /* loading / empty / error の判定ヒント *//* loading / empty / error hints */ },
  "fallback_behavior": {
    "on_version_mismatch": "force_update" | "degrade" | "ignore",
    "on_offline": "cache_last" | "block" | "degrade"
  },
  "meta": {
    "view_id": "search_results",
    "view_version": "1.0.0",
    "min_app_version": "1.2.0",
    "generated_at": "2026-01-01T00:00:00Z"
  }
}

4. 必須ヘッダーと契約 4. Required headers and contract

ヘッダーHeader 用途Purpose BFF 側挙動Server-side behavior
Authorization: Bearer <jwt> Supabase Auth で取得した JWTJWT obtained from Supabase Auth 有効期限切れは 401 → クライアントが refresh → 1 回だけ再試行Expired → 401 → client refreshes → retry exactly once
X-App-Version クライアントのセマンティックバージョン(必須Client semver (required) meta.min_app_version と比較し、古ければ 426 Upgrade Required もしくは fallback_behavior.on_version_mismatch に従うCompared with meta.min_app_version; stale clients get 426 Upgrade Required or the directive from fallback_behavior.on_version_mismatch
X-Device-Id 端末固有 ID(匿名化済み)Anonymized device ID テレメトリ紐付け / レート制限キーTelemetry join key, rate-limit key
X-Device-Locale ja-JP / en-US etc. ui_config の文言選択Drives copy in ui_config
Idempotency-Key UUID v4 / 再送安全化キー(Action / Telemetry で必須)UUID v4; required for Actions / Telemetry KV で重複検知、同一キーは同一結果を返すDedup via KV; same key returns the same response
If-None-Match 前回の ETagPreviously received ETag 未変更時 304 Not Modified → クライアントはキャッシュ表示Returns 304 Not Modified when unchanged; client renders the cached payload
401 → refresh → 再試行 1 回: 401 → refresh → retry once: 401 を受け取ったら Flutter 側で supabase_flutter 経由で refresh token を使って JWT を更新し、同じリクエストを 1 回だけ 再送します。それでも 401 なら Auth 画面へ戻します。無限ループ禁止。 On 401, refresh the JWT via supabase_flutter and retry the original request exactly once. If it still fails, send the user back to the Auth screen. No infinite retry loops.
426 Upgrade Required: 426 Upgrade Required: サーバーが最小バージョン違反を検出すると、ViewEnvelope のかわりに 426 + アップグレード誘導ペイロードを返す場合があります。Flutter は「アップデートしてください」のブロッキング画面を表示し、アプリストアへ誘導します(fallback_behavior.on_version_mismatch = "force_update" のセマンティクス)。 When the server detects a minimum-version violation, it may return 426 instead of a ViewEnvelope. Flutter must show a blocking upgrade screen and deep-link to the app store (fallback_behavior.on_version_mismatch = "force_update").

5. アーキテクチャ図 5. Architecture diagram

シェイプをクリックして関連を強調 Click any shape to focus its connections
flowchart LR
  subgraph Client["Flutter クライアント"]
    UI[UI Layer / Widgets]
    State[Riverpod 3
(flutter_riverpod 3.3.x)] Repo[BFF Client
dio + generated Dart] SecStore[Secure Storage
JWT / refresh] LocalDB[(SharedPreferences +
SecureStorage
SDUI ui_layer cache)] end subgraph Edge["Cloudflare Workers BFF — 唯一の接続先"] Views["GET /v1/mobile/views/home-feed 等
ViewEnvelope"] Actions["POST /v1/mobile/actions/sessions/start 等
ActionEnvelope"] Telemetry["POST /v1/mobile/telemetry/events
TelemetryAck"] Core[Core / Data layers
Hyperdrive → Postgres] Queues[Queues
parky-fcm-dispatch
parky-store-sync] FCMOut[FCM v1 送信] R2Access[R2 / Image Resizing] end subgraph Auth["Supabase Auth — 例外的に直結"] SA["JWT 発行 / refresh
email / OTP / social"] end subgraph Media["画像配信"] R2[(Cloudflare R2)] Resize[Cloudflare
Image Resizing] end subgraph PushIn["プッシュ受信"] FCMIn[firebase_messaging
on device] end UI --> State --> Repo Repo --> Views Repo --> Actions Repo --> Telemetry Repo -.JWT 取得.-> SA Repo -.token 保存.-> SecStore Repo -.キャッシュ.-> LocalDB Views --> Core Actions --> Core Core --> Queues Queues --> FCMOut FCMOut --> FCMIn FCMIn --> UI Repo -->|cached_network_image| Resize Resize --> R2 Core --> R2Access

6. Flutter クライアント構成 6. Flutter client composition

責務は描画・入力・ローカル状態・トークン保持に閉じます。ドメインロジック(料金計算・予約可否等)を Flutter に実装してはいけません。

Flutter's scope is limited to rendering, input, local state, and token storage. Do not implement domain logic (pricing, eligibility, etc.) on the client.

レイヤLayer 採用技術Technology 責務Responsibility
フレームワークFramework Flutter 3.x (Dart) iOS / Android 共通 UIShared UI for iOS / Android
状態管理State management flutter_riverpod 3.3.x ViewEnvelope を provider に写して描画を駆動。Riverpod 3 の AsyncValue.value API を採用(v2 の valueOrNull は使わない)Mirror ViewEnvelope into providers; drive UI. Uses the Riverpod 3 AsyncValue.value API (the v2 valueOrNull form is no longer used)
ルーティングRouting go_router navigation の指示通りに遷移・deep linkFollow navigation hints; handle deep links
HTTP dio + generated Dart client BFF 専用の type-safe クライアント。401 refresh / 426 handling / ETag を一元化Type-safe client for the BFF. Centralizes 401 refresh, 426 handling, ETag use
Auth supabase_flutter Auth 機能のみ使用。DB / RPC / Storage / Realtime は触らないAuth features only. Do not touch DB / RPC / Storage / Realtime
セキュアストレージSecure storage flutter_secure_storage JWT / refresh token / device idJWT / refresh token / device ID
ローカル永続化Local persistence shared_preferences + flutter_secure_storage
(Isar / Hive 未採用 / not in use)
SDUI L0 構造 / L1 ロジックui_layer: navigation shell / screen registry / validation / states / fallback_behavior)を /v1/mobile/views/boot から受けて永続保持。L2 状態data / badge / feature flags)は混ぜず短 TTL or 揮発のみ。実装は core/sdui/ui_layer_cache.dart。Isar / Hive は現在未導入で、必要量の少ない key-value は SharedPreferences、機微情報は flutter_secure_storage に保管しているSDUI L0 structure / L1 logic (ui_layer: navigation shell / screen registry / validation / states / fallback_behavior) sourced from /v1/mobile/views/boot are persisted via core/sdui/ui_layer_cache.dart. L2 state (data / badge / feature flags) is kept short-TTL / in memory. Isar / Hive are not currently used; small KV goes through SharedPreferences and secrets through flutter_secure_storage
地図Maps mapbox_maps_flutter 地図描画・マーカー・ジェスチャー(データは BFF 由来)Rendering, markers, gestures (data comes from the BFF)
位置情報Location geolocator / permission_handler 現在地取得 → BFF に lat/lng を送って検索結果を受けるCapture location → send lat/lng to the BFF and receive results
画像Images image_picker / cached_network_image 撮影 → BFF の presigned URL へ PUT / 表示は Cloudflare Image ResizingCapture → PUT to a BFF-issued presigned URL; serve via Cloudflare Image Resizing
プッシュ通知受信Push receipt firebase_messaging device token を取得 → BFF /v1/mobile/actions/push-tokens/register に登録。表示・deep link ハンドリングは Flutter 側Obtain device token → register via BFF /v1/mobile/actions/push-tokens/register. Display and deep-link handling stay in Flutter
分析Analytics Sentry / BFF telemetry クラッシュは Sentry、プロダクト計測は /v1/mobile/telemetry/eventsSentry for crashes; /v1/mobile/telemetry/events for product analytics
国際化i18n flutter_localizations + ui_config 固定 UI は Flutter l10n、可変文言は ui_configStatic UI uses Flutter l10n; dynamic copy comes from ui_config
決済Payments In-App Purchase (StoreKit / Play Billing) 購入レシートを BFF /v1/mobile/actions/premium/verify-iap に渡して検証Send the receipt to BFF /v1/mobile/actions/premium/verify-iap for validation

推奨レイヤ構成 Recommended layer layout

mobileapp/prototype/flutter/lib/
├── main.dart                       # Supabase 初期化 / dio 構築 / Riverpod root# Supabase init / dio bootstrap / Riverpod root
├── app.dart, app_router.dart       # go_router 定義 + ルートシェル# go_router config + root shell
├── config/                         # Supabase / BFF base URL / dart-define-from-file# Supabase / BFF base URL / dart-define-from-file
├── core/
│   ├── auth/                       # SessionController(Supabase Auth ラッパ)# SessionController (supabase_flutter wrapper)
│   ├── bff/                        # bff_client / view_envelope / action_envelope / async_swr / navigation_resolver# bff_client / view_envelope / action_envelope / async_swr / navigation_resolver
│   ├── sdui/                       # view_client / boot_revalidator / ui_layer_cache(SDUI L0/L1 永続化)# view_client / boot_revalidator / ui_layer_cache (SDUI L0/L1 persistence)
│   ├── i18n/, codes/, theme/       # 静的辞書 / コードマスタ / テーマ# Static dictionary / code master / theme
│   ├── notifications/, iap/, app_review/, observability/, telemetry/
│   ├── storage/                    # SharedPreferences / flutter_secure_storage ラッパ# SharedPreferences / flutter_secure_storage wrappers
│   └── widgets/                    # 共通 widget(envelope_consumer 等)# Shared widgets (envelope_consumer etc.)
├── features/                       # 1 機能 = data / application / presentation の 3 層# Per-feature trio: data / application / presentation
│   ├── splash/, onboarding/, auth/, permissions/
│   ├── home/, search/, search_presets/, destinations/, lot_detail/
│   ├── start_parking/, parking_session/, sessions/, history/, parking_completed/
│   ├── feedback_satisfied/, feedback_dissatisfied/
│   ├── saved/, notifications/, session_notifications/
│   ├── profile/, premium/, rewards/, gamification/, themes/
│   ├── media/, policies/, support/, lot_registration/, app_review/, error_fallback/
├── screens/                        # 旧構造(features/ へ段階的に移行中)# Legacy layout (being migrated into features/)
├── generated/parky_api/            # OpenAPI から生成された Dart クライアント(path 依存パッケージ)# OpenAPI-generated Dart client (path package)
└── widgets/, services/             # BottomNavShell・OS 連携サービス# BottomNavShell + OS-integration services

7. バックエンド側の構成 7. Backend composition

コンポーネントComponent 実体Implementation 役割Role
モバイル BFFMobile BFF Cloudflare Workers (Hono)
parky/api/src/bff/mobile/
Views / Actions / Telemetry の 3 surface。core / data 層に委譲Views / Actions / Telemetry surfaces. Delegates to the core / data layers
Hyperdrive Cloudflare Hyperdrive → Supabase Postgres Workers からの PG 接続プールConnection pool from Workers
Supabase Auth JWT 発行者JWT issuer Flutter だけが直接叩く例外The only surface Flutter talks to directly besides the BFF
Queues parky-fcm-dispatch / parky-store-sync FCM fan-out・ストア同期の fan-outFCM fan-out and store-sync fan-out
FCM v1 Firebase Cloud Messaging BFF が parky-fcm-dispatch consumer から送信Sent from the parky-fcm-dispatch consumer in Workers
画像 / オブジェクトImages / objects Cloudflare R2 + Image Resizing レビュー写真 / メモ写真 / アバター。公開 URL は cdn.parky.co.jpReview / memo / avatar images. Public URL is cdn.parky.co.jp
観測Observability Cloudflare Observability / Sentry ログ・エラー・パフォーマンスLogs, errors, performance

8. キャッシュ・オフライン戦略 8. Caching and offline strategy

9. 通知パイプライン 9. Notification pipeline

flowchart LR
  Trigger["BFF イベント
(review reminder /
session alert /
admin push 等)"] Q["parky-fcm-dispatch
(Cloudflare Queue)"] Consumer["Consumer Worker
FCM v1 呼出"] FCM[(Firebase Cloud Messaging)] Device["Flutter
firebase_messaging"] Token["/v1/mobile/actions/push-tokens/register"] Trigger --> Q --> Consumer --> FCM --> Device Device -->|device_token| Token --> Q

10. 関連ドキュメント 10. Related docs