設計最佳做法#
在前面的章節中,我們概述了透過 Web API 傳輸資料的各種方法。現在你已經熟悉了傳輸的整體面貌,也了解如何在不同模式與框架之間做出選擇,本章將提供一些實務上的最佳做法,幫助開發者從你的 API 中獲得最大價值。
以真實使用場景為設計基礎#
設計 API 時,最好根據**具體且真實的使用場景(use cases)**來做決策。
思考一下使用你 API 的開發者:
- 他們應該能用你的 API 完成哪些任務?
- 開發者應該能建構什麼類型的應用程式?
- 對某些公司而言,目標可能非常明確,例如「開發者應該能對客戶信用卡扣款」
- 對其他公司來說,答案可能更開放,例如「開發者應該能建立一整套互動式、消費者等級的應用程式」
定義好使用場景後,務必確認開發者確實能透過你的 API 做到這些事。API 常常根據應用程式的內部架構來設計,導致實作細節外洩,這會讓第三方開發者感到困惑,造成糟糕的開發者體驗。因此,重點不在於暴露公司內部基礎架構,而是聚焦於外部開發者與 API 互動時應有的體驗。具體的使用場景定義方式,可參考第五章的「Outline Key Use Cases」章節。
開始設計時,很容易在實作與測試之前就想像許多「如果怎樣怎樣」。雖然這些問題在腦力激盪階段很有用,但它們可能讓設計偏離方向,誘使你試圖一次解決太多問題。選定一個具體的工作流程或使用場景,你就能聚焦於一個設計,然後測試它是否適合你的使用者。
專家建議 — Google 開發者倡議者 Ido Green 被問到什麼讓 API 變得優秀時,他的首要答案是專注:「API 應該讓開發者能把一件事做得非常好。這聽起來不如想像中簡單,你需要清楚知道 API 不會做什麼。」
如果你需要將開發者受眾縮小到特定族群,可參考第八章。
打造優秀的開發者體驗#
就像我們花時間思考透過使用者介面(UI)傳遞的使用者體驗一樣,思考透過 API 傳遞的**開發者體驗(Developer Experience, DX)**同樣重要。開發者放棄 API 的門檻很低,糟糕的體驗會導致流失。反過來說,易用性只是留住開發者的最低標準。好的體驗會讓開發者愛上你的 API——他們會成為最有創意的創新者,也會成為你 API 的傳道者。
讓入門變得快速且簡單#
開發者必須能快速理解你的 API 並上手。開發者使用你的 API,可能是為了避免自己建構次要產品套件來支撐主要產品。別讓他們因為 API 難以理解、難以使用而後悔這個決定。
專家建議 — Stripe 開發者關係負責人 Romain Huet:「無論我們多麼謹慎地設計和建構核心 API,開發者總是會創造出我們意想不到的產品。我們給予他們自由去建構他們想要的東西。設計 API 就像設計交通網路——好的 API 不是規定終點或目的地,而是擴展開發者對可能性的想像。」
**文件(Documentation)**對於幫助開發者入門大有助益。除了列出 API 規格的文件外,提供以下資源也很有幫助:
- 教學(Tutorial):互動式介面,教導開發者了解你的 API,可能讓開發者回答問題或在輸入區填寫「程式碼」
- 入門指南(Getting Started Guide):比規格書更有情境脈絡的文件,通常針對剛開始使用的開發者,有時也適用於升級或從一個版本 / 功能轉換到另一個的時機
在某些情況下,你可以提供線上互動式文件來補強易用性,讓開發者有一個沙箱(sandbox)來測試你的 API(更多沙箱資訊請參考第九章)。開發者通常可以使用這些介面測試程式碼和預覽結果,甚至不需要實作認證。圖 4-1 展示了 Stripe 的 UI 範例。

Figure 4.1: Developers can try the Stripe API without signing up
除了互動式文件之外,**軟體開發套件(SDK)**等工具也能大幅幫助開發者使用你的 API。這些程式碼套件旨在簡化部分交易層與應用程式設定,讓開發者能快速啟動專案。
理想情況下,開發者應該不需登入或註冊就能試用你的 API。如果無法避免,你應該提供一個簡單的註冊或應用程式建立流程,僅收集最低限度的必要資訊。如果你的 API 受 OAuth 保護,你應該提供一種方式讓開發者能在 UI 中產生 access token。實作 OAuth 對開發者來說相當繁瑣,如果缺乏簡便的 token 產生方式,你會在這個環節看到顯著的流失率。
追求一致性#
你希望你的 API 具有直覺上的一致性(consistency),這應該反映在以下方面:
- 端點名稱(endpoint names)
- 輸入參數(input parameters)
- 輸出回應(output responses)
開發者應該能在不閱讀文件的情況下,猜出你 API 的部分設計。除非你正在進行重大版本升級或大型發布,否則在設計現有 API 的新功能時,最好追求一致性。
命名一致性的例子: 假設你之前將一組資源命名為「users」並據此命名 API 端點,但現在你認為叫「members」更合理。改用「正確的」新名稱確實很誘人,但如果物件本質相同,在 URI 元件、請求參數和其他地方有時稱為「users」、有時稱為「members」,會讓開發者非常困惑。對大多數漸進式變更而言,與現有設計模式保持一致才是最佳選擇。
型別一致性的例子: 如果在某些地方你有一個名為 user 的回應欄位,有時它的型別是整數 ID,有時又是物件,那麼每個收到這兩種回應的開發者都需要檢查 user 到底是 int ID 還是物件。這種邏輯會導致開發者程式碼膨脹,帶來次優的體驗。
一致性也會影響你自己的程式碼。如果你維護 SDK,你將需要加入越來越多邏輯來處理這些不一致,以便為開發者提供無縫的介面。不如在 API 層級就保持一致,而非為相同事物引入新名稱。
一致性意味著整個 API 中重複出現一系列模式與慣例,讓開發者不用看文件就能預測如何使用你的 API。這涵蓋了資料存取模式、錯誤處理到命名等各方面。一致性之所以重要,是因為它降低了開發者理解你 API 時的認知負擔(cognitive load),幫助現有開發者以減少程式碼分歧的方式適應新功能,也幫助新開發者快速上手你已建構的所有功能。
讓除錯變得容易#
設計 API 的另一個最佳做法是讓開發者容易排除問題(troubleshooting)。這可以透過返回有意義的錯誤訊息以及建構工具來實現。
有意義的錯誤訊息#
錯誤可能發生在程式碼路徑中的許多地方:
- API 請求期間的授權錯誤(authorization error)
- 特定實體不存在時的業務邏輯錯誤(business logic error)
- 較低層級的資料庫連線錯誤(database connection error)
設計 API 時,應透過系統性地組織和分類錯誤以及它們的回傳方式,讓除錯盡可能簡單。不正確或不清楚的錯誤令人沮喪,可能負面影響 API 的採用率——開發者可能卡住然後直接放棄。
有意義的錯誤具備以下特性:
- 容易理解
- 明確無歧義
- 可操作(actionable)——幫助開發者理解問題並解決它
提供包含細節的錯誤訊息能帶來更好的開發者體驗。機器可讀(machine-readable)的錯誤碼字串讓開發者能在程式碼中以程式方式處理錯誤。除了這些字串外,加入更長篇幅的錯誤說明也很有用,這些說明可放在文件中或回應 payload 的其他地方,有時被稱為人類可讀(human-readable)錯誤。
案例:Stripe API 的個人化錯誤訊息
更好的做法是為每個開發者個人化這些錯誤訊息。例如,使用 Stripe API 時,如果你在正式模式中使用測試金鑰,它會回傳如下錯誤:
No such token tok_test_60neARX2. A similar object exists in test mode, but a live mode key was used to make this request.
這類錯誤訊息明確告知開發者問題的根源與解決方向。
以下表格列出不同情境下的推薦與不推薦錯誤碼:
| 情境 | 推薦 | 不推薦 |
|---|---|---|
| Token 已被撤銷導致認證失敗 | token_revoked | invalid_auth |
| 名稱超過最大長度 | name_too_long | invalid_name |
| 信用卡已過期 | expired_card | invalid_card |
| 已退款的交易無法再次退款 | charge_already_refunded | cannot_refund |
要開始設計錯誤系統,你可以沿著 API 請求的程式碼路徑繪製後端架構。目的不是暴露後端架構,而是分類發生的錯誤並辨識哪些應該暴露給開發者。從 API 請求發出的那一刻起,完成請求需要哪些關鍵動作?將 API 請求過程中發生的各種高層級錯誤類別列出來:
| 錯誤類別 | 範例 |
|---|---|
| 系統層級錯誤(System-level error) | 資料庫連線問題、後端服務連線問題、致命錯誤 |
| 業務邏輯錯誤(Business logic error) | 被限流(rate-limited)、請求完成但找不到結果、因業務原因拒絕存取 |
| API 請求格式錯誤(API request formatting error) | 缺少必要的請求參數、組合的請求參數一起使用時無效 |
| 授權錯誤(Authorization error) | OAuth 憑證對該請求無效、Token 已過期 |
將錯誤類別分組後,思考對這些錯誤而言,什麼層級的溝通才有意義。選項包括:
- HTTP 狀態碼和標頭(headers)
- 機器可讀的「codes」
- 較詳細的人類可讀錯誤訊息(放在回應 payload 中)
確保錯誤回應的格式與非錯誤回應一致。例如,如果成功請求回傳 JSON,錯誤也應該以相同的 JSON 格式回傳。
你可能也需要一種機制,將來自服務邊界的錯誤向上傳遞為一致的格式。例如,你依賴的服務可能有各種連線錯誤,你可以讓開發者知道出了問題並請他們重試。
大多數情況下,你要盡可能具體以幫助開發者採取正確的下一步行動。但有時你可能需要回傳更通用的錯誤來隱藏原始問題——這可能是基於安全原因。例如,你大概不希望將資料庫錯誤直接暴露到外部世界,洩漏太多關於資料庫連線的資訊。
以下表格展示了如何將錯誤組織為狀態碼、標頭、機器可讀碼和人類可讀字串:
| 錯誤類別 | HTTP 狀態碼 | HTTP 標頭 | 錯誤碼(機器可讀) | 錯誤訊息(人類可讀) |
|---|---|---|---|---|
| 系統層級錯誤 | 500 | – | – | – |
| 業務邏輯錯誤 | 429 | Retry-After | rate_limit_exceeded | “You have been rate-limited. See Retry-After and try again.” |
| API 請求格式錯誤 | 400 | – | missing_required_parameter | “Your request was missing a {user} parameter.” |
| 授權錯誤 | 401 | – | invalid_request | “Your ClientId is invalid.” |
當你開始組織錯誤時,可能會發現一些可以建立自動化訊息的模式。例如,你可以在 API schema 中定義必要參數,並使用一個程式庫在請求開始時自動檢查,同時格式化回應 payload 中的詳細錯誤訊息。
你會需要在網路上公開記錄這些錯誤。你可以將其建入 API 描述語言(參考第七章)或文件機制中。在撰寫文件之前,先想清楚各個錯誤層級,因為如果錯誤類型繁多,描述多重因素會變得很複雜。你也可以考慮使用詳細的回應 payload 連結到公開文件,讓開發者獲得更多關於所收到錯誤的資訊以及如何復原。
關於有意義的錯誤和 HTTP API 問題詳情的更結構化和詳細的建議,可參考 RFC 7807。
建構工具#
除了讓開發者容易排除問題外,你也應該透過建構內部和外部工具來讓自己的工作更輕鬆。
在 HTTP 狀態碼、錯誤及其頻率以及其他請求 metadata 上進行日誌記錄(logging),無論對內部還是外部使用,在排除開發者問題時都非常有價值。圖 4-2 展示了 Stripe 的 dashboard,其中包含詳細的日誌記錄,讓開發者能方便地排除問題。市面上有許多現成的日誌記錄解決方案,但在實作時,在排除即時流量問題之前,務必尊重客戶隱私,將任何**個人可識別資訊(PII, Personally Identifiable Information)**進行脫敏處理。更多關於幫助開發者除錯和排除問題的開發者工具,請參考第九章。

Figure 4.2: The Stripe API dashboard with request logs
除了日誌記錄之外,建構 API 時,建立 dashboard 幫助開發者分析 API 請求的匯總 metadata 也很有幫助。例如,你可以使用分析平台來:
- 排名最常使用的 API 端點
- 識別未使用的 API 參數
- 分類常見錯誤
- 定義成功指標
如同日誌記錄,市面上也有許多現成的分析平台。你可以用高層級 dashboard 以時間序列的方式呈現資訊。例如,你可能想顯示過去一週每小時的錯誤數量。此外,你可能也想為開發者提供完整的請求日誌,包含原始請求的詳細資訊、是否成功或失敗,以及回傳的回應。
讓你的 API 具備可擴展性#
無論你的 API 設計得多好,隨著產品演進和開發者採用量增加,變更與成長的需求總是存在。這意味著你需要透過建立演進策略來讓 API 具備可擴展性(extensibility)。這讓你作為 API 提供者和你的開發者生態系統都能持續創新,同時也提供了處理**破壞性變更(breaking changes)**的機制。
專家建議 — GitHub 生態系統工程總監 Kyle Daigle:「API 應該提供能啟用新工作流程的基本元件(primitives),而不是單純映射你應用程式的工作流程。API 的建立就像一道閘門,決定了使用者能做什麼。如果你提供過於低層級的存取,可能導致令人困惑的整合體驗,並將過多工作推給整合者。如果你提供過於高層級的存取,最終大多數整合可能只是複製你自己應用程式所做的事。你需要找到正確的平衡,以啟用你在應用程式或 API 中都未曾考慮過的工作流程,從而實現創新。」
早期回饋與 Beta 計畫#
可擴展性的一個面向是確保你已為頂級合作夥伴建立回饋機會(更多關於頂級合作夥伴的資訊,請參考第十章)。你需要一種方式來:
- 釋出特定功能或欄位
- 讓特定的特權開發者能測試這些變更,而不將變更發布給大眾
- 有些人稱此為「Beta」或「早期採用者(early adopter)」計畫
這類回饋在幫助你判斷 API 設計是否達成目標方面極其寶貴。它讓你有機會在採用率普及之前做出修改,避免重大變更帶來大量溝通或營運負擔。
版本管理#
在某些情況下,你可能需要為 API 建立版本(versioning)。
- 越早將版本系統納入設計越好——越晚實作越複雜
- 隨著時間推移,更新程式碼庫的依賴模式以讓舊版本維持向後相容會越來越困難
- 版本管理的好處:允許你在新版本中進行破壞性變更,同時為舊版本維持向後相容性(backward compatibility)
**破壞性變更(breaking change)**是指一旦實施,會導致現有應用程式無法繼續像之前一樣正常運作的變更。
案例:Slack 的轉譯層(Translation Layer)
2017 年,Slack 推出 Enterprise Grid 產品,這是先前產品的聯邦式模型。由於這次聯邦化,Slack 必須從根本上改變使用者資料模型,讓使用者可以屬於多個「工作空間(workspace)」。
在 API 中,使用者之前只有一個 ID。但在新的聯邦式模型中,每個使用者有一個主要(全域)使用者 ID 用於 Enterprise Grid,以及每個工作空間的本地使用者 ID。當現有團隊遷移到 Enterprise Grid 產品時,他們的使用者 ID 將會改變,這會破壞任何依賴固定使用者 ID 的第三方應用程式。
當 Slack 工程團隊意識到這個問題時,他們回到起點重新思考如何為第三方開發者維持向後相容性。最終團隊決定建立一個轉譯層(translation layer)。這個額外的基礎設施會靜默地將使用者 ID 轉譯為與開發者之前收到的一致。
雖然建構這個轉譯層的決定讓 Enterprise Grid 產品的上線延遲了數個月,但確保 API 維持向後相容對 Slack 而言是至關重要的任務。
維持向後相容性#
對於企業和產品仰賴的公司而言,維持向後相容版本是一項艱難的要求。這對不常更新的應用程式尤其如此:
- 許多企業軟體沒有專人負責更新版本
- 公司也沒有動機僅因為你發布了新版本就投資更新
- 許多連網硬體產品也使用 API,但硬體不一定有更新軟體的機制
- 硬體可以使用很長時間——想想你上一台電視或路由器用了多久
維持版本確實有成本。如果你沒有能力支援舊版本長達數年,或者你預期 API 很少變更,可以跳過版本管理,採用增量變更策略(additive change strategy),在單一穩定版本中維持向後相容性。
如果你預期未來任何時候會有重大破壞性變更和更新,我們強烈建議建立版本管理系統。即使需要數年才會進行第一次重大版本變更,至少系統已經準備就緒。在一開始就建立版本管理系統的開銷,遠低於在緊急需要時才加入。
案例:Twitch 棄用 API
2018 年,線上影音串流平台 Twitch 決定棄用(deprecate)一個 API 並提供新的 API。在宣布舊 API 的棄用和停止服務(end of life)後,Twitch 收到了大量開發者回饋,表示他們需要更多時間來處理破壞性變更,否則他們的整合將會中斷。
由於這些回饋,Twitch 決定延長舊 API 的停止服務期限,給予開發者充分的時間將程式碼遷移到新的 API。
結語#
滿足使用者需求是穩健 API 設計的核心。本章涵蓋了多項最佳做法,幫助你打造出色的開發者體驗:
- 以真實使用場景為基礎進行設計
- 讓入門快速且簡單
- 追求 API 的一致性
- 讓除錯和排除問題變得容易
- 讓 API 具備可擴展性
在建構 API 和開發者生態系統的過程中,你可能會發現更多適用於你公司、產品和使用者的特定最佳做法。在第五章中,我們將把這些想法付諸實踐,帶你走過使用本書至今所學知識來設計 API 的實際步驟。