API 設計實務 (API Design & Versioning)

★★★★Draft

FromDesigning Web APIs

📚 From the Books

API 設計實務 (API Design & Versioning)ConceptCommunication

API 一旦有人用,就是改不動的對外契約。所以兩件事要在開頭就做對:用真實使用場景把它設計得一致好懂,以及趁早把版本系統建起來——因為版本越晚補越貴,維持舊版向後相容的成本會隨時間一路攀升。

🧠 Core Ideas

TIP

一致性的最高標準:開發者不看文件也能猜對你 API 的下一個端點怎麼命名、參數怎麼傳、回應長什麼樣。一致性降低的是認知負擔,而認知負擔就是開發者流失的源頭。

⚖️ Tradeoffs

一致性——不看文件也能猜對
  • 讓開發者建立一個心智模型:回應物件嚴格型別、同一 model 在每個端點都長一樣、跨端點共用相同請求模式、失敗方式可預期。
  • 反例:Slack 的 channels.join 收頻道名稱channels.invite 收頻道 ID——逼開發者同時存兩者並自己寫一層邏輯決定用哪個。
  • 不一致會根深蒂固:初版發布後的每次新增都可能引入分歧,開發者一旦採用就拔不掉了。
  • CI 自動化測試守住:在合併前驗證輸入、預期行為、回應 payload 的資料型別,攔下向後不相容的變更。剛導入時先設非阻塞、偽陰性降到 0% 再轉阻塞。
錯誤設計——有意義、可操作、格式一致
  • 好的錯誤要容易理解、明確無歧義、可操作。同時給機器可讀碼(讓程式判斷)與人類可讀訊息(給人看)。
  • 錯誤碼要具體:token_revoked 勝過 invalid_authexpired_card 勝過 invalid_card
  • 沿請求路徑把錯誤分類——系統層級、業務邏輯、請求格式、授權——再決定各自用什麼 HTTP 狀態碼 / 標頭 / 錯誤碼 / 訊息表達(如業務限流回 429 + Retry-After、格式錯回 400、授權錯回 401)。
  • 鐵則:錯誤回應格式要與成功回應一致(成功是 JSON,錯誤也要是同樣的 JSON)。盡量具體,但必要時為安全回傳通用錯誤,別把資料庫錯誤直接外洩。更結構化的做法可參考 RFC 7807。
相容性方向——新增最容易、移除最貴
  • 新增最安全,但仍要問兩件事:這欄位以前是否「不存在」而有人依賴它不存在?是否該讓開發者選擇加入(新參數/新端點)而非強加給所有人?
  • 血淚案例:Slack 為了一致性把 is_bot 改成一律回傳,但有個熱門 app 是靠「這個鍵存不存在」跑邏輯的,變更上線後當場崩潰——最後只能回滾、給三個月緩衝。教訓:不要用你「以為」的使用方式來推變更。
  • 移除/棄用代價最高:要用「胡蘿蔔」(把獨家新功能只放在新端點)誘導遷移,並給充足的棄用時程——太短會侵蝕信任。Salesforce 承諾每版至少支援三年、棄用前至少提前一年通知。
  • 追溯相容性難在你不知道哪些欄位正被用:REST 通常整包 JSON 都回,你只知道全部被請求了。Cloudinary 之所以能維持完全向後相容,是因為一開始就把介面設計成可擴展。
版本方案與棄用——把變更打包成可理解的區塊
  • 累加式變更策略:只新增、永不改型別或移除(除非讓人選擇加入),維持單一穩定版本;若你沒能力支援舊版數年、或 API 很少變,這反而省事——單層 API 的可維護性價值不該被低估
  • 明確版本策略的三種方案:
    • URI 組件api.uber.com/v1.2/requests、Twitter /2/accounts——好綁定、瀏覽器可直接除錯;但暗示資源永久性。
    • HTTP Header:Stripe 的 Stripe-VersionAccept: …;version=1——省 URI 膨脹,但瀏覽器難實驗、可能影響快取。
    • 請求參數:Google Maps ?v=3——路由較難。
  • 幕後實作三招:版本化函式名版本化 controller版本間轉換層。Stripe 用滾動版本(以日期命名),開發者首次請求即被釘選到當時最新版,之後可用 header 覆蓋。
  • 溝通要分級:向後相容的變更(新增欄位/參數/端點)走 RSS + 文件即可;向後不相容的(移除欄位、改型別、改行為)要 email 受影響開發者、發部落格,並留足通知期(範例框架給到 18 個月)。用 SemVerMAJOR.MINOR.PATCH)把相容性寫進版號。

🔑 Takeaways

✍️ My Notes

No notes yet — jot your takeaways or Q&A here.

📖 Further Reading