ADR-0002 檢核與交接放在應用層,不放在 Stored Procedure
已採納 · 2026-09-09
我維護過的進件流程是這樣運作的:
業務人員在進件頁面按下儲存,前端呼叫一支 Stored Procedure。所有檢核都寫在那支 SP 裡, 全部通過之後,SP 才會往另一張資料表插入一筆——那一筆的存在,就是「這件案子已經進件」 的依據。下游系統看那張表決定要不要接手。
這個做法能動,而且改起來很快:檢核條件要調整,直接改 SP,不用重新部署應用程式。 但用久了之後,有三個問題會浮出來。
一、改得快,但追不回來
Section titled “一、改得快,但追不回來”SP 不在版本控制裡。檢核條件改了之後,沒有紀錄說明是誰改的、什麼時候改的、為什麼改。 偶爾發生誤改,要復原只能靠記憶,或是某個人手邊剛好留存的舊版本。
「這條規則當初為什麼是這樣」這個問題,在 SP 裡沒有地方可以回答。
二、複製出來的 SP 會慢慢分岔
Section titled “二、複製出來的 SP 會慢慢分岔”T-SQL 沒有好的模組化手段,想複用一段檢核邏輯,最省事的做法就是複製一支新的 SP 出來改。 久了就會有好幾支長得很像但不完全一樣的 SP。
真正的代價不是「檔案變多」,是它們會分岔:本來一樣的兩支,其中一支改了, 另一支沒改。行為從此不一致,而且沒有任何機制會告訴你。要維護時得先花時間確認 這幾支到底哪裡不一樣。
三、檢核越長,使用者越有感
Section titled “三、檢核越長,使用者越有感”檢核和寫入綁在同一個同步請求裡。SP 的指令一長,執行時間就拉長, 業務人員按下儲存之後會明顯感覺到延遲。
而檢核只會越加越多,不會變少。所以這個問題會隨時間惡化。
在這個 API 裡,把三件事分開:
檢核放在應用層,進版本控制。 驗證規則寫在 ApplicationEndpoints.Validate,
每次變更都有 commit、有訊息、有 PR 可以回頭看。
規則只有一份。 允許的狀態轉移集中在 StateMachine 這一個型別裡,
沒有「複製一份改一改」這條路可以走。
交接用顯式契約,不用資料表。 進件的結果是一個 HTTP 回應,不是「某張表多了一筆」。
驗證失敗當場回 400 並列出是哪個欄位、為什麼,而不是靜靜地不插入。
入口只做快速檢核。 POST /v1/applications 只確認資料收得下,立刻回 201,
狀態是 Received。真正的規則判斷延後到批次(ADR-0004)。
使用者不必等那些他等不起的東西。
三個問題各自對應到上面一項,不需要額外解釋。值得多說的是最後一項:
把「使用者等得起的」和「等不起的」分開,是同步長交易唯一真正的解法。 在 SP 裡優化指令可以爭取一些時間,但檢核只會越加越多——這是在跟趨勢對抗。 把重的工作移出請求路徑才是結構上的修正。
- 改規則要重新部署。 這是實實在在的損失。改 SP 是立即生效的,改應用層要走一次 建置與部署。換來的是可追溯與 review,但「線上出事要馬上調參數」這種場景確實變慢了。
- 多一層網路邊界。 資料表交接和寫入在同一個交易裡,不會有「插入成功但通知失敗」 這種狀態。改成 HTTP 之後,呼叫端必須處理逾時與重試。
- DBA 不再看得到全貌。 規則從資料庫移到應用程式碼,原本熟悉 SP 的人失去了 直接檢視規則的能力。這在以 DBA 為中心的團隊是真實的組織成本。
考慮過的替代方案
Section titled “考慮過的替代方案”把 SP 納入版本控制。 用遷移工具管理資料庫物件,SP 的變更就進得了 git。 這能解決第一個問題,而且改動範圍比重寫小很多。如果我今天要在原系統上做改善, 我會先做這件事——它成本最低、效益最直接。但它解決不了分岔與同步延遲。
維持 SP 檢核,只把慢的部分改成非同步。 針對第三個問題的最小修正。 在無法大改的系統裡這是務實的選擇,但前兩個問題原封不動。
什麼時候 SP 內檢核仍然是對的
Section titled “什麼時候 SP 內檢核仍然是對的”這篇不是在說「SP 爛、應用層好」。SP 內檢核有它成立的條件,而且不少見:
- 單一資料庫、單一寫入端。 檢核和寫入在同一個交易裡,一致性是免費的。 改成跨網路的服務反而要自己處理部分失敗。
- 團隊以 DBA 為中心。 規則放在最熟悉它的人看得到的地方。
- 變更頻率低。 追溯性的價值和變更頻率成正比。一年改兩次的規則, 沒有版控的痛感遠低於一週改兩次。
- 多個異質的上游都要寫入同一份資料。 檢核放在資料庫,是唯一保證每個上游 都遵守的位置。放在某一個應用程式裡,其他上游可以繞過。
最後一項特別值得注意:把檢核移到應用層,前提是所有寫入都經過那一層。 這個 API 成立是因為它是唯一入口。在一個有批次匯入、有其他系統直接寫入的環境裡, 這個前提不成立,那麼檢核就該留在資料庫。