この記事の目的
Cloudflare WorkersとSupabaseを採用する際に、特にデータベース関連の技術選定(ORM、マイグレーション、スキーマ管理など)で迷った経験はありませんか?本記事では、筆者が実際に直面した課題と試行錯誤の過程を赤裸々に綴ります。
この記事が、同じような構成で開発を進める方々にとって、未来の自分からの「欲しかった情報」となることを目指します。
2025/08/11 phase7追記
目次
技術選定の要素と構造
技術選定の主要要素
現代のWebアプリケーション開発では、以下の要素が相互に影響し合いながら技術スタックを形成します:
本プロジェクトで検討した要素
- Driver: データベース接続方式(TCP vs HTTP, プロトコル選択)
- ORM: オブジェクト関係マッピング(型安全性、開発体験)
- Migration: スキーマ変更管理(バージョニング、チーム協調)
- Schema管理: スキーマ定義の真実の源泉(Code-first vs DB-first)
- Seed: 初期データ・テストデータ管理
- Transaction: トランザクション制御方式(App Layer vs DB Layer)
固定条件
Cloudflare Workers
- 理由: 第一線で活躍するエンジニアからの推薦による。エッジコンピューティングが実現するパフォーマンスと、極めて快適な開発体験に魅力を感じたため。
Supabase (PostgreSQL)
- 理由: 当時の技術トレンドであったことに加え、MySQLからPostgreSQLへ移行したいという個人的な関心があったため。
技術選定の変遷
Phase 1: Supabase オールイン戦略
初期構想: Supabase ですべてを解決
要素 | 選択 | 理由 |
---|---|---|
Driver | @supabase/supabase-js | プラットフォーム統合 |
ORM | なし(生API) | Supabase API で十分 |
Migration | Supabase CLI | Supabaseオール・イン |
Schema管理 | Supabase CLI | Supabaseオール・イン |
Seed | なし | 初期段階では不要 |
Transaction | なし | 特別な技術が必要だとは想定していなかった |
📝 振り返り・学び
単純に技術選定のための知識、特にトランザクションに関する理解が不足していた。
Phase 2: 宣言的スキーマ管理の必要性
💭 当時の状況・思考
この時点では、宣言的にスキーマを管理するという手法を知らなかった。
また、ベンダーロックインを懸念し、DBを切り離して汎用的に管理したいという意図があった。
※ 宣言的スキーマ管理とは
宣言的: コードでスキーマの「あるべき姿」を定義し、ツールが現状との差分を自動計算・適用
Migration管理: 変更を順次SQLファイルで記録し、履歴順に適用
例: Prismaschema.prisma
→ 自動でALTER文生成 vs 手動で001_add_users.sql
作成
※ ベンダーロックインとは
特定のクラウドサービスに依存しすぎて、他のサービスへの移行が困難になること
例: Supabaseダッシュボードでスキーマ管理 → 移行時にSupabase固有設定の再構築が必要
❌ 問題発生
ベンダーロックインとスキーマ管理
✅ 解決策・決断
Prisma でスキーマ管理
要素 | 変更前 | 変更後 | 理由 |
---|---|---|---|
Schema管理 | Supabase CLI | Prisma Schema | 宣言的管理、VC対応 |
ORM | 生API | 検討中 | 型安全性向上 |
📝 振り返り・学び
宣言的管理という概念は以前から漠然と認識していたが、この時初めて深く調査し、その後の開発に大きな影響を与えた。この宣言的な考え方から発想して、Seedデータも決定論的に管理するアプローチを採用した。
※ 決定論的Seedデータ管理
UUIDを決定論的に生成 → 何度実行しても同一データ
例:user_id: uuid("admin-user")
→ 常に同じUUIDが生成され、全環境で一致
Phase 3: 権限管理とマイグレーションの複雑化
❌ 問題発生
Prisma では DB 権限やfunctionを宣言的に管理できない
Prismaでpushしたスキーマに対し、APIからアクセスを試みたところ、権限不足でテーブルにアクセスできない問題が発生した。Prismaのスキーマ定義はテーブル構造のみに特化しており、データベース権限(GRANT/REVOKE)やPostgreSQL関数を宣言的に管理することができない※。やむを得ず、Supabase CLIによるマイグレーションを導入するに至った。
※SupabaseのAPI経由でアクセスする場合、public schemaへのGRANT権限が必要だった
✅ 解決策・決断
Supabase CLI でマイグレーション導入
この時点でreset-db
コマンドはかなり複雑化した。この状況を改善したいと考えつつも、当時は開発の進行を優先した。
要素 | Phase2 | Phase3 | 理由 |
---|---|---|---|
Migration | Prisma Migrate | Supabase CLI | 権限管理対応 |
Schema管理 | Prisma Schema | Prisma + SQL | ハイブリッド管理 |
📝 振り返り・学び
プロトタイプの開発を優先するあまり、この時点での技術選定を深く検討せず、力技で開発を進めてしまった。最終的にこの構成は変更することになったが、時には実装を優先すべき局面もあり、まさにここがそのタイミングだったと今では考えている。
Phase 4: トランザクション制御の限界
💭 当時の状況・思考
購入関連機能の実装を進める中で、Supabaseのクライアントライブラリでは、複数のDB操作を一つのトランザクションとしてまとめられないという制約に直面しました。
SupabaseのクライアントはHTTPベースで動作するため、以下のように記述したコードはそれぞれが独立したリクエストとして実行されます。そのため、途中の処理でエラーが発生しても、それ以前の操作をロールバック(取り消し)できません。
❌ 問題発生
HTTP ベースでアプリケーション層トランザクション不可
❌ Supabase公式の推奨策とその課題
Supabaseでは、このような一連の処理を**DB Function(ストアドプロシージャ)としてデータベース層に定義し、それをクライアントから呼び出す方法を推奨しています。この方法であれば、一連の処理の原子性(アトミック性)**が保証されます。
※ アトミック性
関連する一連の処理が**「すべて成功」するか「すべて失敗(実行前の状態に戻る)」するかのどちらか**であることが保証される性質。
しかし、このアプローチを試みた結果、新たな課題が浮上しました。
-
複雑化するロジック管理
- 複雑なビジネスロジックをSQLで記述する必要があり、可読性やメンテナンス性が低下する。
- デバッグが困難になる。
-
開発プロセスの非効率化
- 機能が増えるたびにDB Functionが増加し、マイグレーションファイルでのバージョン管理が非常に煩雑になる。
🔍 試行錯誤・調査
当初はSupabaseの思想に沿って開発を進めていましたが、DB Functionの管理コストが現実的ではないと判断し、他の方法を探し始めました。宣言的に管理できるツールとして「Atlas」も候補に挙がりましたが、npmパッケージではなかったため採用には至りませんでした。
振り返りと学び
今回の経験から、SupabaseのDB Functionによるトランザクション管理は、以下のようなケースでは有効な選択肢だと考えられます。
- 小規模なアプリケーションで、トランザクション処理が少ない場合
- 処理ロジックが単純で、変更頻度が低い場合
一方で、複雑なビジネスロジックや頻繁な仕様変更が求められるプロジェクトでは、DB Functionに依存しすぎると管理コストが膨らむ可能性があります。プロジェクトの特性に応じて、アーキテクチャを慎重に選択することの重要性を学びました。
Phase 5: Driver とORMの選択
💭 当時の状況・思考
この段階に至り、初めてDriverとORMの選定に本格的に着手した。
🔍 試行錯誤・調査
検討した選択肢:
- Prisma Client: 学習コストがなく理想的だが、Cloudflare Workersに非対応。クエリも独特。
- Drizzle ORM: Workersに対応しており、型安全性も高い。クエリがSQLに近く、馴染みやすかった。
❌ Prisma Client の制約
✅ Drizzle ORM の採用
要素 | Phase3 | Phase4 | 理由 |
---|---|---|---|
Driver | Supabase Client | postgres.js | TCP直接接続、Workers対応 |
ORM | なし | Drizzle | 型安全性、Workers対応 |
Transaction | HTTP制限 | App Layer | 柔軟な制御 |
Phase 6: ツール乱立による複雑化と、理想への葛藤
ツール乱立による複雑化:
- Prisma: スキーマ定義・開発時DB管理、Seedデータ挿入
- Supabase CLI: マイグレーション・権限管理
- Drizzle: 本番ORM・型生成
- postgres.js: 接続クライアント
現在の開発フロー例:(コマンドで統一済)
理想: Drizzle 統一
❌ 現実的な制約: Prisma の開発体験が良すぎる
Prismaによるスキーマ管理の体験は極めて優れている。schema.prisma
はコード量が少なく直感的であり、AI(LLM)にコンテキストとして与える際のトークン量削減にも寄与する。開発期間中はこの利点を最大限に活用したいという思いがあった。
現在の妥協案(Phase 6時点)
要素 | 開発時 | 本番時 | 移行予定 |
---|---|---|---|
Schema管理 | Prisma Schema | Drizzle型生成 | Drizzle Schema |
Migration | Prisma Migrate | Drizzle Kit | Drizzle Kit |
ORM | - | Drizzle | Drizzle |
Driver | - | postgres.js | postgres.js |
Seed | Prisma Script | - | Drizzle Script |
Phase 7: 開発環境のリセットフロー簡略化(現在)
2025/08/11 追記
💭 当時の状況・思考
Phase 6まで到達した時点で、開発時のDBリセットコマンドが非常に複雑になっていた。特に問題だったのは、dev環境でSupabaseのauthスキーマに対する権限エラーが頻発し、回避するためにsupabase reset → prisma push → migration削除 → migration up
という複雑な手順を踏む必要があった点だ。
❌ 問題発生
Supabase resetによるauth権限エラー
✅ 解決策・決断
authスキーマには触らない戦略
問題の本質を分析した結果、以下のことが判明した:
- 構造変更(DDL): Supabaseが管理するauthスキーマのテーブル構造変更は権限エラー
- データ操作(DML): service_roleでauth.usersのレコード操作は問題なし
この理解に基づき、supabase reset
を完全に削除し、Prismaとdrizzleで必要な処理のみを行うように変更した。
🔍 Drizzleの疑似マイグレーション活用
さらに重要な発見があった。当初、Drizzleへの完全移行は本番運用開始まで不可能だと考えていた。なぜなら:
- Prismaでスキーマ管理している限り、Drizzle migrationは使えない
schema.ts
が存在しないため、正式なマイグレーション生成ができない
しかし、Drizzleを疑似マイグレーションツールとして活用できることに気づいた:
この方法により、実質的にDrizzleでほぼ全ての処理を管理できるようになった:
- 関数・トリガー定義:
0001_auth_hook.sql
など - 権限設定:
0003_granted_service_role.sql
- auth.usersクリア:
0000_skip_auth_schema.sql
(環境変数制御)
要素 | Phase6 | Phase7 | 理由 |
---|---|---|---|
リセット処理 | supabase reset + 複雑な手順 | prisma push + drizzle apply | authスキーマ回避 |
auth.users管理 | 不明確 | 環境変数制御 | 本番安全性確保 |
Migration役割 | Supabase CLI | Drizzle(疑似) | 実質的な統一 |
SQL管理 | 分散(Supabase/Prisma) | Drizzle functions/に集約 | 管理の一元化 |
📝 振り返り・学び
この改善により、開発時のDBリセットが大幅に簡略化された。重要な学びは:
- 権限エラーの本質を理解することで、適切な回避策を見つけられた
- 不要な処理を削除(supabase reset)することで、処理時間も短縮
- 環境変数による制御で、開発と本番の挙動を安全に分離
- 疑似マイグレーションという発想で、完全移行前でもDrizzleに実質統一
開発中はデータを入れ直すことを前提とした割り切りと、ツールの柔軟な活用により、Phase 6で悩んでいた「ツール乱立」問題が大幅に改善された。本番運用時の正式なDrizzle migration移行への道筋も明確になった。
今後の構想
本番運用に向けた移行計画
- Phase 8: Prisma Schema → Drizzle Schema 変換
- Phase 9: Drizzle Kit での完全なマイグレーション管理
もし今から始めるなら:Neon DBで構築する理想構成
※これはあくまで仮定の話で実際にneonを使用していないものの想像です
理想的な技術選定プロセス
なぜNeon DBか:
Neon DBを選択する最大の理由は、現状のプロジェクトでSupabaseの多機能性を活かしきれていない点にある。Storage、Supabase CLI、Edge Functionは使用していないし、Authは利用しているものの、他のツールで十二分に代替可能であるように思う。一方、Neonが提供する無料のDBブランチ機能やサーバーレス環境での自動スケーリングは、本プロジェクトにとって非常に魅力的に見える。
段階的な理想構成:
開発期間中(本番運用まで):
注意: 開発期間中はdb-reset
前提のため、Prisma push後の他ツールマイグレーション実行は問題なし。履歴管理不要で任意のツール組み合わせが可能。
本番運用開始時:
要素 | 開発期間中 | 本番運用時 | 理由 |
---|---|---|---|
DB | Neon DB | Neon DB | Serverless最適化、ブランチング |
Driver | @neondatabase/serverless | @neondatabase/serverless | HTTP、Workers完全対応 |
ORM | Drizzle | Drizzle | 軽量、型安全、Workers対応 |
Migration | Prisma Migrate | Drizzle Kit | 開発効率 → 一貫性 |
Schema | Prisma Schema → introspect | Drizzle Schema | AI開発用 → 実行用 |
Seed | Prisma Script | Drizzle Script | 開発効率 → 環境一貫性 |
移行戦略の利点:
- 開発期間: Prismaの優れた開発体験を活用
- 本番運用: Drizzleの統一性とパフォーマンスを享受
- 段階的移行: リスクを最小化しながら最適化
Schema管理のハイブリッド戦略:
AI適正の観点:
- Drizzle Schema: 型安全な実行・マイグレーション用
- Prisma Schema: 1ファイルで全体構造把握、AI開発時の参照用
- 最高の開発体験: 実行効率 + AI可読性の両立
避けるべき選択
- 段階的移行: 最初から最終形を目指す
- 複数ツール混在: 開発・本番環境の分離を避ける
- 過度な最適化: 早すぎる最適化より一貫性重視
まとめ: 技術選定の教訓
重要な原則
-
基盤システムから設計する: トランザクション制御のような基盤技術は、後から変更すると全体のアーキテクチャに大きな影響を与える。UI開発に入る前に、データ操作の基本パターンを明確にし、それに適したツール選定を行う。
-
最低限の移行余地を残す: DB層の分離(例:Drizzle接続の抽象化)など、過度ではない最小限の設計で将来の変更に対応する。
-
複数ツールの試行錯誤を恐れない: 様々なツールを試すことで各ツールの特性や制約を深く理解でき、最終的により適切な選択と効率的な実装につながる。
-
フェーズに応じた最適化: 開発期間中は開発効率を、本番運用時はパフォーマンスと一貫性を重視するなど、プロジェクトのフェーズに応じて優先度を調整する。
避けるべき落とし穴
- 最初から完璧を求める: 要件が変化する前提で柔軟な設計を心がける
- 単一ツール至上主義: ツールの制約に振り回されるより、要件に最適な組み合わせを選ぶ
- ツール統一の強制: 開発フェーズを無視した一貫性の追求は非効率
この変遷の記録が、Cloudflare WorkersとサーバーレスDBという同様の環境で開発を進める方々の参考となれば幸いです。