API 設計 — 如何設計穩定可預測的 API (談冪等性)?
2024年10月17日
在之前的文章 API 設計 — 好的 API 設計有什麼特點? 中,我們討論了好的 API 有什麼特點,其中有提到兩點是我們認為需要更多版面來談的,分別是可相容性與可預測性。之所以說這兩點特別重要,是因為當沒有處理好,會導致後續很多問題。在這篇文章中,我們會針對「可預測性」來討論。
本篇的 E+ 完整版有公開免費閱讀,有興趣的讀者可以前往此連結
可預測性:讓 API 穩定可預測
在前後端的世界中,因為溝通不會隨時都是穩定的,例如客戶端的裝置可能出問題、網路可能中間突然斷掉一小段時間,所以在前後端用 API 溝通時,不能只考慮正常的情境 (俗稱 happy path),而是要進一步去考慮出特別的問題時該怎麼處理 (俗稱 edge case)。當能夠去處理各類極端狀況,API 與整個系統將能夠更穩定。
舉例來說,當今天你用一個電子信箱產品,在編寫完草稿後,按下儲存按鈕,不管按一次,還是按十次,都是執行儲存當下的狀態,不會因為你按了十次,中間網路不穩,就存成十份不同的草稿。如果真的是這樣的話,使用者體驗會不太好。
又或者今天假如使用者在電商網站結帳時,中間網路有一度不穩,使用者等了一下看頁面沒反應,以為自己沒按成功,所以多按了一次結帳,這時不會要讓使用者付兩次款。如果變得要付兩次款,那肯定沒有使用者會想用這個電商的產品。
要能夠做到穩定,冪等 (idempotent) 是很關鍵的要點。所謂的冪等性,是指 API 的呼叫或者操作,不論做多少次,都會是相同的結果;或者換個角度說要做到不論請求幾次,API 都不會產生副作用。當能夠做到冪等性,就能夠確保在重試時,不論重試幾次,都能確保只有執行一次,這樣能避免當遇到各類狀況,導致的不必要重複。
事實上,過去業界就有因為沒有處理好 API 冪等,導致造成重大事故的案例。
具體來說,先前 Uber 支付組的工程經理 Gergely Orosz 就曾公開分享,當年 Uber Eats 在印度有個重大事故,是某段時間內,綁定印度最大支付商之一 Paytm 的使用者,即使 Paytm 帳戶沒有餘額,也可以無限地在 Uber Eats 上下單。
Gergely 在分享中談到,會出現這個事故,是當年 Paytm 的 API 做了改動。在改動前,Paytm 的 API 一直是維持冪等的,所以 Uber 的支付團隊在串接時,就預設 API 是冪等的,沒有多做處理。
然而 Paytm 在那次看似無害的改動中,沒有維持 API 的冪等性,這造成的問題是,當 Uber 呼叫 Paytm 的 API 時,第一次因為使用者的餘額不足所以回傳原本預期的錯誤,但這時如果使用者再下一次單,Paytm 會回傳另一種錯誤訊息。
兩次回傳不同的錯誤訊息,看似很無害,但是偏偏因為第二種錯誤訊息原本 Uber 團隊不知道,所以沒處理,因此在 Uber 端就讓這種下單通過。而當使用者發現沒餘額時,只要按兩次就變得能下單成功,當時印度各大學迅速傳開,讓 Uber Eats 在短時間被大量下免費的單,而這造成的商業損失非常可觀。
如何讓 API 有冪等性?
相信看完上面的故事,讀者們已經意識到冪等性的重要。如果 Paytm 的 API 在遇到餘額不足,是穩定回傳 Uber 端可以處理的錯誤訊息。
這時下個問題會是如何讓 API 有冪等性?
以 RESTful API 來說,有些請求相對不用擔心冪等性問題。舉例來說,GET
請求就是,因為假如某個資料存在伺服器,不論請求幾次,資料沒變的狀況下,就會都拿到一樣的資料。
PUT
也是,因為 PUT
是一次修改整個資源,假如有多個請求送來,就以最後送到的請求即可。同樣地 DELETE
刪除某個資源後,就沒有該資源,多發幾個過來的結果都是該資源被刪除,所以也是冪等。
然而,我們很常用的 POST
請求會是相對需要特別處理的。就像電商下訂單時的支付,通常會是用 POST
請求。而最常見的冪等處理方式會是加上冪等鑰 (idempotent key)。所謂的冪等鑰,是一個獨特的 id
,讓伺服器端知道這個請求已經被處理過了。
所以如果有網路中斷,或者使用者快速連擊,當同一個請求帶著相同的冪等鑰,伺服器端就知道不用再處理該請求。在系統設計中,遇到追問如何在分散式系統中,避免請求被重複處理,冪等鑰是最基本一定要想到的解法。
舉例來說,全球支付 API 龍頭之一的 Stripe,在 API 設計中,就有冪等鑰的欄位:
具體來說,假如要呼叫 CreatePayment
的 API,客戶端可以先產生一組冪等鑰 (例如用 uuid 來產生),這時如果使用者重複點擊,因為帶著的是同一組冪等鑰,所以伺服器端知道已經處理過,就不會重新處理這個支付請求。
先前 Stripe 有一篇《Designing robust and predictable APIs with idempotency》技術文,深入淺出地談了如何透過冪等鑰來提高 API 可預測性,非常推薦一讀。
閱讀更多
如果你對於「如何設計好 API」這主題感興趣,我們在 E+ 有寫更深入詳細的內容,包含可相容性、如何做好向後相容 (backward compatible)。有興趣的讀者,歡迎加入 E+ 成長計畫。
本篇的 E+ 完整版有公開免費閱讀,有興趣的讀者可以前往此連結