この記事の目的
Cloudflare WorkersとSupabaseを採用する際に、特にデータベース関連の技術選定(ORM、マイグレーション、スキーマ管理など)で迷った経験はありませんか?本記事では、筆者が実際に直面した課題と試行錯誤の過程を赤裸々に綴ります。
この記事が、同じような構成で開発を進める方々にとって、未来の自分からの「欲しかった情報」となることを目指します。
目次
技術選定の要素と構造
技術選定の主要要素
現代のWebアプリケーション開発では、以下の要素が相互に影響し合いながら技術スタックを形成します:
本プロジェクトで検討した要素
- Client: データベース接続方式(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 ですべてを解決
要素 | 選択 | 理由 |
---|---|---|
Client | @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 | ハイブリッド管理 |
📝 振り返り・学び
プロトタイプの開発を優先するあまり、この時点での技術選定を深く検討せず、力技で開発を進めてしまった。最終的にこの構成は変更することになったが、時には実装を優先すべき局面もあり、まさにここがそのタイミングだったと今では考えている。
第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: Client とORMの選択
💭 当時の状況・思考
この段階に至り、初めてClientとORMの選定に本格的に着手した。
🔍 試行錯誤・調査
検討した選択肢:
- Prisma Client: 学習コストがなく理想的だが、Cloudflare Workersに非対応。クエリも独特。
- Drizzle ORM: Workersに対応しており、型安全性も高い。クエリがSQLに近く、馴染みやすかった。
❌ Prisma Client の制約
✅ Drizzle ORM の採用
要素 | Phase3 | Phase4 | 理由 |
---|---|---|---|
Client | 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の極端な変更がありえるうちはschema.prismaで管理
要素 | 現在の仕組み |
---|---|
Schema管理 | Prisma Schema |
Migration | Supabase CLI |
ORM | Drizzle (DB introspect) |
Client | postgres.js |
Seed | Prisma Script |
今後の構想
本番運用に向けた移行計画
- Phase 7: DB Functionをアプリケーションコードに移行
- Phase 8: Prisma Schema → Drizzle Schema 変換
- Phase 9: Drizzle Kit での完全なマイグレーション管理
各要素の移行予定
要素 | 移行予定 |
---|---|
Schema管理 | Drizzle Schema |
Migration | Drizzle Kit |
ORM | Drizzle |
Client | postgres.js |
Seed | Drizzle Script |
もし今から始めるなら: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最適化、ブランチング |
Client | @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適正の観点:
- Prisma Schema: 1ファイルで全体構造把握、AI開発時の参照用
- 最高の開発体験: 高速scrap&build + AI可読性の両立
まとめ: 技術選定の教訓
重要な原則
-
基盤システムから設計する: トランザクション制御のような基盤技術は、後から変更すると全体のアーキテクチャに大きな影響を与える。UI開発に入る前に、データ操作の基本パターンを明確にし、それに適したツール選定を行う。
-
最低限の移行余地を残す: DB層の分離(例:Drizzle接続の抽象化)など、過度ではない最小限の設計で将来の変更に対応する。
-
複数ツールの試行錯誤を恐れない: 様々なツールを試すことで各ツールの特性や制約を深く理解でき、最終的により適切な選択と効率的な実装につながる。
-
フェーズに応じた最適化: 開発期間中は開発効率を、本番運用時はパフォーマンスと一貫性を重視するなど、プロジェクトのフェーズに応じて優先度を調整する。
避けるべき落とし穴
- 最初から完璧を求める: 要件が変化する前提で柔軟な設計を心がける
- 単一ツール至上主義: ツールの制約に振り回されるより、要件に最適な組み合わせを選ぶ
- ツール統一の強制: 開発フェーズを無視した一貫性の追求は非効率
この変遷の記録が、Cloudflare WorkersとサーバーレスDBという同様の環境で開発を進める方々の参考となれば幸いです。