設計最佳做法#

在前面的章節中,我們概述了透過 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_revokedinvalid_auth
名稱超過最大長度name_too_longinvalid_name
信用卡已過期expired_cardinvalid_card
已退款的交易無法再次退款charge_already_refundedcannot_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
業務邏輯錯誤429Retry-Afterrate_limit_exceeded“You have been rate-limited. See Retry-After and try again.”
API 請求格式錯誤400missing_required_parameter“Your request was missing a {user} parameter.”
授權錯誤401invalid_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 的實際步驟。