前言
Shopify 到 Ragic 的 Mapping 上線後,商家仍可能刪除欄位、改成唯讀、把 Link & Load 欄位換掉,或調整日期與數字型別。若 App 只在第一次設定時驗證,之後仍照舊 Field ID 寫入,最危險的結果不是明確報錯,而是資料進了錯誤欄位。
上一篇談 Shopify 歷史訂單 Backfill。回補大量資料前,必須先確定 Mapping 與目前 Ragic schema 相容;因此案例把「重新匯入 API 文件」變成一次 schema diff 與人工修復流程。
原本的做法與問題症狀
早期做法只保存 Mapping JSON。重新貼入新版 Ragic API 文件時,App 會解析出最新欄位,但既有 Mapping 仍維持 active。商家看得到新 schema,卻不知道哪些同步規則已經失效。
另一個看似方便的方案是重新跑名稱配對,直接覆蓋舊 Mapping。問題是欄位同名不代表業務語意相同;unique key、是否允許覆寫、值域與主表/子表責任都不能靠字面猜測。自動修復一旦選錯,後續 Webhook 與 Backfill 會一起放大錯誤。
問題是怎麼推敲出來的?
來源專案先建立 diffMappingAgainstSchema(),以 Ragic Field ID 比對每個 Mapping field 與 unique key。測試逐步加入四類問題:
missing_field:Field ID 已不存在,或整個 sheet 找不到。readonly_field:欄位仍存在,但已不可寫入。loaded_field:欄位由 Link & Load 提供,不應直接寫入。type_changed:Mapping 使用日期或數字 transform,但目前欄位型別可能不相容。
重新儲存連線與 API 文件後,系統會用最新 parsed sheets 檢查該連線下的 active mappings。只要有 issue,就把 Mapping 標成 needs_review,保存 issue details 與一筆 MappingVersion。
同步查詢只取 active Mapping,因此 needs_review 不只是 UI badge,而是停止有風險寫入的 gate。
最後採用的架構
流程分成偵測、阻擋、修復與稽核:
重新匯入 Ragic API 文件
→ parse latest sheets / fields
→ diff active Mapping by Field ID
→ compatible:保持 active
→ incompatible:needs_review + issue version
→ 暫停該 Mapping 的同步
→ 商家查看問題與建議
→ 逐欄選擇替代欄位或重建 Mapping
→ 建立新的 active MappingVersion
已提交版本曾在 Dashboard 顯示 issue code、Ragic 欄位、Shopify path、摘要與建議,讓商家不只看到「Mapping 壞了」,而是知道哪個欄位消失、變唯讀或型別不相容,以及應該重新匯入文件、選擇替代欄位或檢查 Ragic 設定。研究當下的來源工作樹正在重整這段 UI,因此這裡只把 server functions、版本資料與既有測試視為可證明能力,不宣稱目前未提交介面已完成。
關鍵實作
Schema diff 不依賴顯示名稱作為 identity:
const current = fieldsById.get(mappingField.ragicFieldId);
if (!current) {
issues.push({ code: 'missing_field', shopifyPath: mappingField.shopifyPath });
} else if (!current.writable || current.isReadonly) {
issues.push({ code: 'readonly_field', shopifyPath: mappingField.shopifyPath });
} else if (current.isLoadedField) {
issues.push({ code: 'loaded_field', shopifyPath: mappingField.shopifyPath });
}
逐欄修復仍在 server 端驗證替代欄位:
const replacement = latestSheet.fields.find(
field =>
field.id === replacementFieldId &&
field.writable &&
!field.isReadonly &&
!field.isLoadedField,
);
if (!replacement) throw new Error('替代欄位不可用');
若修的是 unique key 對應的 Shopify path,unique key 也必須一起更新;同一 Ragic Field ID 已被另一條 Mapping 使用時則拒絕儲存。
實際驗證
來源測試已覆蓋:
- 相容 schema 不改變 active Mapping。
- mapped field 或 unique key 消失時標成
needs_review。 - 唯讀、loaded 與不相容型別會產生不同 issue code。
- issue details 可還原成商家可讀的欄位、Shopify path 與建議。
- 欄位修復只能選最新 schema 中可寫入的欄位。
- 修復 unique key field 時同步更新 unique key。
- 替代 Field ID 已被其他 Mapping 使用時拒絕。
- 每次建立、封存、needs review、修復與還原都保存版本事件。
- 還原版本只查詢目前 shop 可存取的歷史,不接受其他 shop 的版本 ID。
這些驗證說明 App 內的 parsed schema 與 Mapping lifecycle;它們不能保證 Ragic 所有 formula、workflow、approval 或 Link & Load 行為都能由目前 parser 表達。
仍然存在的限制
第一,案例使用的是專案定義的 Ragic API 文件模板,不是直接讀取 Ragic 官方 schema endpoint。Ragic 現行 HTTP API 已提供單一 sheet 的 metadata/schema,可回傳 Field ID、型別、唯讀/計算欄位與 subtable 等 metadata;但來源案例尚未串接、測試或遷移到這支 API。文件若沒有同步反映 Ragic 現況,現有 diff 只是在比對兩份過期資料。
第二,目前型別相容判斷是有限規則,不涵蓋所有 write format、selection options、subtable row identity、formula 與 workflow。即使 diff 顯示 compatible,大批同步前仍應做 Dry Run 與少量 read-back。
第三,版本還原只還原 App 保存的 Mapping 設定,不會回滾 Ragic 欄位,也不會撤銷已寫入的資料。現有 restoreMappingVersion() 也不會自動重新執行 diff;若 schema 已刪除舊 Field ID,還原舊版本後仍要再以最新 schema 驗證,不能把 restore 當成無條件安全 rollback。
第四,來源中仍保留以規則建議重新產生 Mapping 的修復路徑;對正式商家流程,更安全的預設仍是逐欄人工確認。名稱打分可以當建議,不能直接取代 unique key 與業務語意決策。
可以延伸到哪些情境?
Schema diff 不只適用 Ragic。只要兩個系統靠欄位 Mapping 串接,都可以保存版本化 contract,來源或目的 schema 變更時先標成 needs review,再用 issue-level repair 恢復。
下一篇會把同一套 App 的資料模型帶到部署層:開發用 SQLite、正式用 PostgreSQL:Prisma 雙 Schema 如何避免部署才爆炸?
參考資料
以上平台文件查核日期:2026-07-27。
