システム全体像 System architecture

Parky モバイルアプリは、Cloudflare Workers BFFを介して Supabase に接続する構成です。 管理者ポータル・オーナーポータルと同じ Supabase プロジェクト(PostgreSQL + Auth + Realtime)を共有し、 クライアントが直接叩く API は Workers の /v1/* の 1 経路に一元化します(Auth / Realtime のみ SDK で Supabase を直接参照)。 書き込み可能範囲はコード層の user_id スコープと RLS(Row Level Security)で二重に防御します。

The Parky mobile app connects to Supabase through a Cloudflare Workers BFF. It shares the same Supabase project (PostgreSQL + Auth + Realtime) with the admin portal and owner portal; clients hit a single unified API surface at /v1/* on Workers (only Auth and Realtime use the Supabase SDK directly). Writable surface is gated by code-level user_id scoping and RLS as defense in depth.

モバイルアプリのチャネル接続 Mobile app channel view

モバイルアプリだけに関係する外部接続を抽出した図です。 全体像は 全体アーキテクチャ を参照してください。

The slice of the system that the mobile app actually touches. For the full picture see the overall architecture.

現時点のスコープ: Current scope: この図はプロトタイプ(mobileapp/prototype/flutter/)の pubspec.yaml で実際に取り込まれている依存のみで構成しています。FCM 受信 / Push / 生体認証 / カメラ / 決済 SDK 等は仕様書には挙がっていますが、現在のプロトタイプには未組み込みのため含めていません。 This diagram only shows integrations actually wired into the Flutter prototype (mobileapp/prototype/flutter/pubspec.yaml). Push / FCM receive / biometrics / camera / payment SDKs are called out in the product spec but are not yet in the prototype, so they are omitted here.
シェイプをクリックして関連を強調 Click any shape to focus its connections
flowchart LR
  MA["Mobile App
Flutter prototype"] subgraph BFF["Cloudflare Workers BFF"] API["/v1/* endpoints"] end subgraph Supabase["Supabase (supabase_flutter)"] AUTH["Auth"] DB[("PostgreSQL
+ PostGIS")] RT["Realtime"] end subgraph Storage["Object storage"] CDN["cdn.parky.co.jp
(Cloudflare R2 public GET)"] end subgraph External["外部サービス"] MB["Mapbox
mapbox_maps_flutter"] IMG["cached_network_image"] URL["url_launcher"] end MA --> API API --> DB MA --> AUTH MA --> RT MA --> MB MA --> IMG MA --> URL IMG --> CDN

予定されているが未実装の接続Planned but not yet wired

3.1 システムアーキテクチャ 3.1 System architecture

シェイプをクリックして関連を強調 Click any shape to focus its connections
flowchart TB
  subgraph Client["モバイルクライアント (Flutter)"]
    UI[UI Layer
Widgets / Screens] State[State Management
Riverpod / BLoC] Repo[Repository Layer] Cache[(Local Cache
Isar / Hive)] Sec[Secure Storage
flutter_secure_storage] end subgraph Edge["BFF (Cloudflare Workers)"] BFF[Workers / Hono
/v1/* endpoints] HYP[Hyperdrive
PG connection pool] Q1[Queue
parky-fcm-dispatch] Q2[Queue
parky-store-sync] CRON[Cron Triggers
*/10 min] end subgraph Backend["バックエンド (Supabase)"] Auth[Supabase Auth
JWT / OTP] PG[(PostgreSQL 17
+ PostGIS)] RT[Realtime
WebSocket] end subgraph External["外部サービス"] Mapbox[Mapbox GL
Geocoding / Map Tile] FCM[Firebase Cloud Messaging
Push] R2[(Cloudflare R2
画像・レビュー写真)] AI[LLM Provider
AI検索] Sentry[Sentry
Crash/Error] GA4[Google Analytics 4
計測] end UI --> State State --> Repo Repo --> Cache Repo --> Sec Repo --> BFF Repo --> Auth Repo --> RT Repo --> Mapbox Repo --> R2 BFF --> HYP HYP --> PG BFF --> Q1 Q1 --> FCM BFF --> Q2 CRON --> BFF BFF --> AI PG --> RT RT --> Repo UI --> Sentry UI --> GA4

3.2 クライアント構成 3.2 Client composition

モバイルアプリは Flutter で構築され、iOS / Android 両対応です。 プロトタイプは parky/mobileapp/prototype/flutter/ に存在します。

The mobile app is built on Flutter and targets both iOS and Android. The prototype lives in parky/mobileapp/prototype/flutter/.

技術スタック Technology stack

レイヤLayer 採用技術Technology 用途Purpose
フレームワークFramework Flutter 3.x (Dart) iOS / Android 共通UIShared UI for iOS / Android
状態管理State management Riverpod(推奨)Riverpod (recommended) リアクティブな状態配信・DIReactive state propagation and DI
ルーティングRouting go_router 宣言的ルーティング / DeepLinkDeclarative routing / deep links
HTTP / API supabase_flutter SDK, dio Cloudflare Workers BFF / 外部APICloudflare Workers BFF / external APIs
地図Maps mapbox_maps_flutter 地図表示・マーカー・クラスタリングMap rendering, markers, clustering
位置情報Location geolocator, permission_handler 現在地取得・権限制御Current location and permission handling
ローカルDBLocal DB Isar / Hive オフラインキャッシュ・検索履歴Offline cache and search history
セキュアストレージSecure storage flutter_secure_storage 認証トークン・リフレッシュトークンAuth tokens and refresh tokens
プッシュ通知Push notifications firebase_messaging FCM 受信 / バックグラウンド処理FCM reception / background handling
画像Images image_picker, cached_network_image レビュー写真・メモ写真Review photos and memo photos
分析Analytics firebase_analytics, Sentry イベントログ / クラッシュ / 計測Event logs, crashes, metrics
国際化i18n flutter_localizations ja / en
決済Payments In-App Purchase (StoreKit / Play Billing) プレミアムプランPremium plan

レイヤ構成(推奨) Layer composition (recommended)

mobileapp/
├── lib/
│   ├── main.dart
│   ├── app/                  # ルーティング・テーマ・i18n# Routing, theme, i18n
│   ├── core/                 # 共通ユーティリティ・定数・エラー型# Shared utilities, constants, error types
│   ├── data/
│   │   ├── api/              # Supabase / 外部API クライアント# Supabase / external API clients
│   │   ├── models/           # エンティティ・DTO# Entities and DTOs
│   │   ├── repositories/     # ドメイン別リポジトリ# Domain repositories
│   │   └── local/            # Isar / SecureStorage
│   ├── domain/               # ユースケース・純ドメインロジック(料金計算等)# Use cases and pure domain logic (fee calculation, etc.)
│   ├── presentation/
│   │   ├── pages/            # 画面 25枚# 25 screens
│   │   ├── widgets/          # 再利用ウィジェット# Reusable widgets
│   │   └── providers/        # Riverpod プロバイダ# Riverpod providers
│   └── services/             # 位置情報 / 通知 / バックグラウンド# Location / notifications / background
├── assets/
└── test/

3.3 バックエンド構成 3.3 Backend composition

バックエンドはサーバーレス(Supabase マネージド)で、専用のアプリケーションサーバーは持ちません。 モバイルクライアントは Cloudflare Workers BFF (/v1/*) 経由で DB アクセスし、重い処理は PostgreSQL RPC に切り出します。

The backend is serverless (Supabase managed), with no dedicated application server. Mobile clients hit the Cloudflare Workers BFF (/v1/*), and heavy logic is pushed into PostgreSQL RPCs.

コンポーネントComponent 実体Implementation 役割Role
REST API Cloudflare Workers BFF (/v1/*) 全クライアント共通の API 層。OpenAPI 3.1 で契約、自動生成 Dart クライアントから叩くUnified API surface for all clients; OpenAPI 3.1 contract consumed via the generated Dart client
RPC PostgreSQL ストアドファンクションPostgreSQL stored functions 周辺駐車場検索 (nearby_parking_lots)、料金計算、EXP/バッジ更新等。Workers から Hyperdrive 経由で呼び出しNearby parking lot search (nearby_parking_lots), fee calculation, EXP/badge updates, etc. Invoked from Workers via Hyperdrive
Queues Cloudflare Queues FCM 配信 fan-out (parky-fcm-dispatch)、ストア同期 (parky-store-sync)。各 DLQ 配線済FCM fan-out (parky-fcm-dispatch) and store sync (parky-store-sync). DLQs wired
Realtime Supabase Realtime (WS) アプリ内通知リアルタイム受信、駐車セッション更新Realtime in-app notifications and parking session updates
Auth Supabase Auth メール/パスワード、OTP、ソーシャルログインEmail/password, OTP, social login
Storage (画像)Storage (images) Cloudflare R2 (S3 互換)Cloudflare R2 (S3-compatible) レビュー写真・メモ写真・プロフィールReview photos, memo photos, profiles

3.4 データストア構成 3.4 Data store composition

永続化レイヤ Persistence layer

ストアStore 技術Technology 保持データStored data
プライマリDBPrimary DB PostgreSQL 17 + PostGIS 全エンティティ(管理者ポータルと共有)All entities (shared with the admin portal)
オブジェクトストレージObject storage Cloudflare R2 互換Cloudflare R2 (S3-compatible) 画像、PDF、エクスポートImages, PDFs, exports
クライアントキャッシュClient cache Isar / Hive(Flutter)Isar / Hive (Flutter) 検索履歴、保存条件、オフラインマスター、直近のセッションSearch history, saved filters, offline master data, recent sessions
セキュアストレージSecure storage Keychain / Keystore JWT, リフレッシュトークン, デバイスIDJWT, refresh tokens, device ID
メモリキャッシュIn-memory cache Riverpod (memoized providers) 1画面以内で共有する揮発キャッシュVolatile cache shared within a single screen

キャッシュ戦略 Caching strategy

データ種別Data type TTL 戦略Strategy
駐車場一覧(検索結果)Parking lot list (search results) 60秒60 seconds stale-while-revalidate
駐車場詳細Parking lot detail 10分10 minutes ETag相当(updated_at比較)ETag-equivalent (updated_at comparison)
レビューReviews 5分5 minutes プル更新で即時破棄Invalidated immediately on pull-to-refresh
駐車セッション(駐車中)Parking session (active) 無期限No expiry Realtime更新 + ローカルミラーRealtime updates + local mirror
コードマスターCode master 24時間24 hours 起動時フェッチ + バックグラウンド更新Fetched at launch with background refresh
記事・広告Articles and ads 15分15 minutes pull-to-refresh
ユーザープロフィールUser profile 5分5 minutes 変更操作後即時無効化Invalidated immediately after any mutation

3.5 外部サービス連携 3.5 External service integration

サービスService 用途Purpose 連携方式Integration
Mapbox 地図描画・ジオコーディング・逆ジオコーディングMap rendering, geocoding, reverse geocoding モバイルSDK直接(APIキーはクライアント保持・ドメイン制限)Direct mobile SDK (API key held by the client with domain restrictions)
Firebase Cloud Messaging Push通知配信Push notification delivery Workers BFF の parky-fcm-dispatch キュー経由で送信、クライアントは受信のみDispatched through the Workers BFF via the parky-fcm-dispatch queue; client only receives
Cloudflare R2 画像・写真保存Image and photo storage Workers の /v1/storage/upload-url が署名付き PUT URL を発行、クライアントが直接アップロード。公開 GET は cdn.parky.co.jpWorkers /v1/storage/upload-url mints a signed PUT URL, the client PUTs directly. Public GET via cdn.parky.co.jp
LLM Provider AI検索(自然言語→検索条件変換)AI search (natural language to search filters) Workers の /v1/search/ai 経由(API キー秘匿、AI Gateway でキャッシュ・フォールバック)Routed through Workers /v1/search/ai (API keys kept secret; AI Gateway handles cache/fallback)
In-App Purchase プレミアムプラン課金Premium plan billing StoreKit2 / Play Billing、レシートを Workers の /v1/verify-iap で検証StoreKit2 / Play Billing with receipt verification at Workers /v1/verify-iap
Sentry クラッシュ・エラー収集Crash and error capture Flutter SDK
Firebase Analytics / GA4 イベント計測Event tracking Flutter SDK

3.6 通知インフラ構成 3.6 Notification infrastructure

sequenceDiagram
  participant Admin as Admin Portal
  participant W as Workers BFF
/v1/admin/user-notifications/{id}/send participant DB as PostgreSQL (via Hyperdrive) participant Q as Queue
parky-fcm-dispatch participant C as Queue Consumer
queue/fcm-dispatch.ts participant KV as KV parky-cache participant FCM participant App as Mobile App Admin->>W: POST send W->>DB: user_push_tokens 取得 W->>DB: user_notifications status=sending W->>Q: enqueue (500 tokens/batch) Q->>C: message batch C->>KV: OAuth access_token (cache) C->>FCM: FCM v1 send (parallel fetch) FCM->>App: Push 通知配信 C->>DB: success_count / failure_count 加算 App->>DB: user_notifications.read_at を更新 DB->>App: Realtime で他端末にも反映

3.7 ログ / 監視基盤 3.7 Logging / monitoring

レイヤLayer ツールTool 対象Scope
アプリクラッシュApp crashes Sentry Dart 例外、ネイティブクラッシュ、ソースマップDart exceptions, native crashes, source maps
イベント計測Event tracking Firebase Analytics / GA4 画面遷移、機能利用、KPIScreen transitions, feature usage, KPIs
ユーザー行動ログUser activity logs user_activity_logs テーブル(Postgres)user_activity_logs table (Postgres) 駐車・レビュー・検索等のドメインイベントDomain events such as parking, reviews, and searches
APIエラーAPI errors Sentry + Supabase Logs 5xx, レート超過, 認証失敗5xx, rate-limit hits, auth failures
バックエンドメトリクスBackend metrics Supabase Dashboard DB接続数、クエリ時間、RLS拒否率DB connections, query duration, RLS rejection rate
Push 配信状況Push delivery status FCM Console + Workers Observability(wrangler tail / Logpush)FCM Console + Workers Observability (wrangler tail / Logpush) 配信成功/失敗率Delivery success / failure rate
イベント二重記録の原則:Dual event-logging principle: ドメインイベント(駐車開始・レビュー投稿等)は user_activity_logs にもGA4にも記録します。 前者は KPI / バッジ計算 / バックオフィス閲覧のための正規イベント、 後者は UX 分析・ファネル分析のための分析イベントです。混在しないよう目的で切り分けます。 Domain events (parking start, review submission, etc.) are recorded in both user_activity_logs and GA4. The former is the canonical event used for KPIs, badge calculation, and backoffice browsing; the latter is the analytics event used for UX and funnel analysis. The two are kept separate by purpose so they never blur together.