前言
這個 Shopify App 在本機沿用範本的 SQLite,部署規劃則要求 PostgreSQL。兩邊都能被 Prisma Client 存取,很容易讓人以為只要正式環境換一條連線字串;實際上 datasource provider、migration history、型別能力與部署命令都屬於 artifact 的一部分。
上一篇處理的是 Ragic schema 變更與 Mapping repair。這篇改看 App 自己的資料庫 schema:如何讓開發速度與正式部署需求並存,又不把「本機測試通過」誤當成「PostgreSQL migration 可用」。
原本的做法與問題症狀
專案最初只有 prisma/schema.prisma,provider 是 SQLite,migration lock 也是 SQLite。若 production 只設定 PostgreSQL 連線,Prisma 不會自動把 SQLite migration history 轉成 PostgreSQL;deploy command 仍可能讀到錯的 schema 與 migration 目錄。
另一個做法是直接把共用 schema 的 provider 改成 PostgreSQL。這會讓 production 方向一致,卻打破原本不需額外服務的本機開發與測試。更麻煩的是,兩種 provider 若反覆切換同一套 migration history,reviewer 很難判斷某段 SQL 是在哪個資料庫產生。
問題是怎麼推敲出來的?
來源 Git 歷史先加入 readiness checks:正式環境不能使用 file: datasource,指定的 Prisma schema provider 必須是 postgresql,migration lock provider 也必須一致。
接著新增獨立 production artifacts:
prisma/schema.prisma
prisma/migrations/
→ SQLite,本機開發與測試
prisma/postgresql/schema.prisma
prisma/postgresql/migrations/
→ PostgreSQL,正式 generate 與 migrate deploy
最後在 CI 加入 production schema validate,並用測試比較兩份 schema 的 model 區塊。這樣新增 SyncJob、MappingVersion 或其他 model 時,如果只更新其中一份,CI 會先失敗。
最後採用的架構
雙 Schema 的重點不是複製檔案,而是把執行入口寫死到正確 artifact:
Local
→ SQLite schema
→ SQLite migration history
→ fast tests / local development
Production
→ PostgreSQL schema
→ PostgreSQL migration history
→ generate with explicit schema
→ migrate deploy with explicit schema
CI
→ validate production schema
→ compare model parity
→ tests + typecheck + build
部署 runbook 要先驗證 schema path、migration lock 與 datasource provider,再執行 migration。Web build 成功並不代表資料庫已套用正確 migration;兩者是不同的部署面。
關鍵實作
兩份 datasource 明確分開:
// Local
datasource db {
provider = "sqlite"
url = "file:dev.sqlite"
}
// Production artifact
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Production script 明確指定 schema:
{
"scripts": {
"setup:production": "prisma generate --schema prisma/postgresql/schema.prisma && prisma migrate deploy --schema prisma/postgresql/schema.prisma",
"prisma:validate:production": "prisma validate --schema prisma/postgresql/schema.prisma"
}
}
CI parity test 只比較 model block,避免 datasource 差異造成誤判:
expect(normalizeModels(productionSchema)).toBe(normalizeModels(localSchema));
expect(readMigrationProvider(productionLock)).toBe('postgresql');
公開範例只保留環境變數名稱,不包含正式 datasource、主機、帳號、密碼、project ID 或 connection string。
實際驗證
來源 repository 已有測試與 CI 證據:
- Production schema 檔案存在,datasource provider 是 PostgreSQL。
- Production migration lock provider 是 PostgreSQL。
- Local 與 production schema 的 model 區塊一致。
- Readiness CLI 會拒絕正式環境使用 file-based datasource。
- Readiness CLI 會拒絕 schema provider 或 migration lock 不是 PostgreSQL。
- CI 執行 production Prisma validate,再跑 tests、typecheck 與 build。
- Runbook 明確要求先在 staging 或測試資料庫執行 migration、檢查 SQL、備份,再執行正式 migration 與 smoke test。
Repository 中的 production migration 是可審查 artifact,但沒有公開證據可證明它已在真實正式資料庫執行。本文因此描述的是 deployment readiness,不宣稱 production cutover 已完成。
仍然存在的限制
雙 Schema 最大成本是 drift。現有 parity test 能抓 model 文字差異,卻不能證明 SQLite 與 PostgreSQL 對索引、constraint、transaction、JSON、日期或字串長度的行為完全相同。
第二,CI 的 prisma validate 驗證 schema 語法與 datasource 設定,不會真的套用 migration。更高信心的 gate 應啟動暫時 PostgreSQL,從空資料庫執行整套 migrate deploy,再跑針對 production provider 的整合測試。
第三,兩份 migration history 必須視為不同線路。不能把 SQLite 產生的 SQL 複製到 PostgreSQL 目錄,也不能在正式資料庫用 db push 取代可追蹤的 migration deploy。
第四,若 schema 開始使用 PostgreSQL 專屬型別或 extension,兩份 model 不一定還能維持逐字一致。屆時應改成單一 PostgreSQL 開發環境,或建立明確的 provider abstraction 與差異測試,而不是讓 parity test 阻擋合理設計。
最後,database rollback 不是把 Web deployment 切回上一版就完成。若新版本已寫入資料,必須先判斷 schema 是否向後相容;不可未審查就執行 destructive rollback。
可以延伸到哪些情境?
雙 Schema 適合仍需要輕量本機開發、但正式資料庫已確定不同 provider 的過渡期。若團隊與 CI 能穩定提供 PostgreSQL,長期最簡單的方向通常是開發、測試與正式使用同一 provider,減少只有 production 才出現的差異。
本批同步可靠性文章到這裡完成:從 Webhook job、歷史 Backfill、Ragic schema repair,一路到 App 自己的資料庫部署邊界。
參考資料
- Prisma:Schema location and multi-file schemas
- Prisma:Migrate limitations and known issues
- Prisma:Migration histories
- Prisma:Development and production workflows
以上平台文件查核日期:2026-07-27。
