選單

介面邊界的校驗:該在用戶端還是伺服器端攔

同一套校驗規則該放在用戶端還是伺服器端,取決於你要省的是往返次數還是信任邊界。本文說明兩層各自該負責什麼、錯誤回應怎麼分類、外部查詢失敗時怎麼回退,以及為什麼校驗失敗不等於號碼不存在。

發佈於

  • API
  • 錯誤處理

校驗規則一旦被放進系統,就會立刻遇到一個分工問題:該在用戶端先擋,還是留給伺服器端?兩者的答案不同,而且不是二選一。本文說明兩層各自該負責什麼、錯誤回應怎麼分類才不會誤導呼叫方、外部查詢失敗時該怎麼回退,以及為什麼校驗失敗不能當成號碼不存在。

校驗該放在用戶端還是伺服器端?

兩邊都放,但目的不同。用戶端放的是體驗:在送出去之前就發現明顯的輸入錯誤,省下一趟往返,也讓使用者立刻知道要改哪裡。伺服器端放的是信任邊界:任何客戶端都可以被改寫,所以規則不能只靠前端。

這個分工也決定了兩邊的實作可以不同。前端可以是精簡版,只處理長度與字元集;後端保留完整的規則與依據。但兩邊的結論用語必須一致,否則使用者會在同一個號碼上看到兩種說法。

需要注意的是不要把規則只放在前端。前端校驗是提示,不是保證。任何寫進合約的約束,都必須在伺服器端也成立。

還有一個不對稱的地方值得先講清楚:兩層的版本更新速度不同。用戶端可能是幾週前載入的頁面,伺服器端則隨時可以部署。當規則調整時,舊頁面仍會用舊規則提示使用者,然後被後端以新規則拒絕。若不做版本標記,這種情況會被當成隨機的詭異問題。

邊界上要校驗什麼、放過什麼

不是所有檢查都該在邊界上做。分界線可以這樣畫:能在本地用規則判斷的,就在邊界做;需要外部資料才能回答的,不要塞進校驗步驟。

檢查類型 在哪裡做 理由
型別與必填 邊界 純本地判斷,成本極低
長度與字元集 邊界 純本地判斷,能擋掉大量錯誤
校驗位演算法 邊界 本地可算,且能給出明確依據
是否在冊 另一步 需要外部資料,不該混進校驗
歸屬與狀態 另一步 同上,且涉及權限與時效

把後面兩步拆出去,好處是校驗端點的回應時間與可預測性不再受外部系統影響。更實際的好處是:當外部查詢掛掉時,校驗仍然可以正常回答,而不是整條流程一起失敗。

拆開的另一個好處是可測試性。純本地的判斷只需準備輸入與期望結論,不需要模擬外部系統的各種失敗;而外部查詢的那一步可以單獨用假的回應測試逾時與錯誤路徑。兩者混在一起時,任何測試都得同時鋪好兩邊的條件。

錯誤回應的分類與可重試性

呼叫方最需要知道的不是錯了,而是該不該重試、該改什麼。因此錯誤回應應該同時帶上兩件事:一個穩定的分類碼,以及一句人可以讀懂的說明。

建議的分類至少涵蓋:格式不符、校驗位不成立、無對應制度,以及系統暫時無法處理。前三者重試沒有意義,第四者才值得重試。把系統暫時失敗也回成校驗失敗,會讓呼叫方浪費重試次數,也可能誤刪正確的資料。

還有一點:不要把詳細的內部原因直接回給呼叫方。規則的內部結構、資料表名稱與例外堆疊都屬於實作細節,對外只需要穩定的分類與說明。

分類碼還有一個容易被忽略的要求:長期穩定。呼叫方會依分類碼決定重試與提示策略,因此碼值的含義不能中途改變。若要調整,應該新增一個碼並保留舊的,而不是讓同一個碼在不同版本裡代表不同的事情。

為什麼「校驗失敗」不能當成「號碼不存在」?

因為這是兩件不同性質的事。校驗失敗說的是這個值與它宣稱的制度不自洽,是一個由算式得到的結論;號碼是否存在說的是這個值有沒有被登記,是一個需要查詢的事實。

把兩者畫上等號,最常見的後果是把合法資料判成無效。使用者在輸入時多打一位、少打一位,或把兩個欄位貼在一起,都會造成校驗失敗,但這些都和號碼本身是否存在無關。

反過來也一樣:校驗通過不代表號碼存在。任何人知道規則就能替任意前綴算出合法的校驗位,所以通過只證明自洽,不證明登記。契約裡如果寫著校驗通過即視為有效,那句話本身就是錯的。

限流、逾時與外部查詢的回退

當校驗流程裡含外部查詢,就必須處理對方不回應的情況。原則是:把外部查詢當成可能失敗的相依,而不是必然成功的步驟。

回退的方向有兩種。一種是降級回答:只回報本地能確認的部分,並明確標示外部查詢未完成。另一種是延後處理:先接受輸入,把查詢排進佇列稍後再補。兩者都優於讓整個請求失敗。

逾時與限流則要在呼叫端處理,而不是假設對方不會擋。收到限流回應時應該退讓並重試;收到逾時則要區分對方沒收到與對方處理了但回覆沒回來,這決定了重試是否安全。

不論哪一種回退,都要在回應裡讓呼叫方知道資料目前處於什麼狀態。默默回一個看起來成功的結果,是最糟的選項。

回退還有一個容易被忽略的方向:把查詢與校驗的結果分開存放。這樣即使外部查詢暫時失敗,使用者之後仍可只重跑查詢那一步,而不必把整個流程重來。分開存放也讓你可以統計查詢的失敗率,而不必從整體成功率裡猜。

給開發者:契約、版本與日誌

把校驗做成服務之後,最貴的失誤通常發生在契約與日誌兩處。

  • 契約要寫清楚結論的取值與含義,尤其是僅格式與無規則的差異。
  • 回應要帶規則的版本,讓呼叫方能解釋同一筆資料為何前後結論不同。
  • 日誌不要記錄完整的號碼,需要的話只記錄分類與足以定位的摘要。
  • 測試資料一律使用構造值,不要把真實號碼寫進測試檔或範例。
  • 回應要能區分本地判定與外部查詢的結果,不要讓兩者的來源混在一起。

日誌這一項特別容易失手。為了除錯而把完整值寫進日誌,等於在校驗服務裡開了一個新的資料出口,而這個出口通常沒有存取控制。需要追查個案時,用去識別化的識別碼回查原始來源,比在日誌裡留完整值安全得多。

下一步

盤點你目前的校驗流程,把本地可算與需要外部查詢兩類檢查分開,並確認前者的回應不依賴後者。接著檢查錯誤回應的分類碼,確認呼叫方能從回應本身判斷該不該重試。想驗證分類是否符合預期,可用號碼校驗工具對照同一組構造值。四態結論的定義讀沒有校驗位的號碼;批次情境下的對應做法讀批次校驗工作流。

本文的介面示例一律使用構造值;真實號碼不該出現在日誌、範例或測試用例中,校驗結果也不構成對任何號碼真實性或可用性的保證。

繼續閱讀

卡號與身分證號校驗工具相關文章