システム全体像 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.
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.
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
- Push 通知受信 (FCM) — 仕様書の「通知機能」に記載。現時点で
firebase_messagingはpubspec.yamlに未追加。Workers 側はparky-fcm-dispatchキュー + consumer で FCM v1 を叩く配線が完成済みのため、端末側が追加されれば配信経路は完成する見込み。 - Push notifications (FCM) — called out in the spec's "通知機能".
firebase_messagingis not yet inpubspec.yaml. The server side is already wired (queueparky-fcm-dispatch+ consumer against FCM v1); adding the device side would close the loop. - カメラ / 写真添付 — 仕様書の「駐車中メモ機能」「駐車後メモ機能」「レビュー機能」で写真添付が想定されている。プロトタイプには
image_picker/camera等は未導入。 - Camera / photo attach — "駐車中メモ", "駐車後メモ", and the review feature all imply photo attachment.
image_picker/camerapackages are not yet in the prototype. - 決済 (Stripe / IAP) — 仕様書の「プレミアムプラン画面」で課金が想定されている。プロトタイプには決済 SDK が未導入、DB スキーマ側には
revenue_transactions.stripe_payment_intent_id等の受け皿のみ存在する。 - Payments (Stripe / IAP) — implied by the "プレミアムプラン" screen in the spec. No payment SDK in the prototype today; the DB side has
revenue_transactions.stripe_payment_intent_idcolumns reserved. - 位置情報 / 駐車セッション — Mapbox SDK 自体はマップ上のユーザー位置表示に使えるが、駐車開始時の GPS ピン固定などのバックグラウンド取得は未実装。
- GPS / parking session capture — Mapbox can show the user on the map, but background GPS capture (e.g. pinning a parking spot on session start) is not yet implemented.
3.1 システムアーキテクチャ 3.1 System architecture
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 で他端末にも反映
- デバイストークン管理:起動時に FCM トークンを取得し、
user_push_tokensテーブルに upsert(user_id+device_idユニーク) - Device token management: fetch the FCM token at launch and upsert it into the
user_push_tokenstable (unique onuser_id+device_id) - 送信元:管理者ポータル(マーケ通知)、DB トリガ(料金アラート等システム通知)、スケジューラ(定期通知)
- Senders: the admin portal (marketing notifications), DB triggers (system notifications such as fee alerts), and the scheduler (recurring notifications)
- 配信チャネル:Push(FCM)+ アプリ内通知(
user_notificationsテーブル + Realtime) - Delivery channel: Push (FCM) plus in-app notifications (the
user_notificationstable + Realtime) - 開封計測:Push 受信時にローカルで
delivered_at、タップでopened_atを記録 - Open-rate measurement: record
delivered_atlocally on push reception andopened_aton tap
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 |
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.