第五章:實際設計(Design in Practice)#
前幾章涵蓋了 API 範式、安全性與最佳實踐的指引,現在是動手進行 實務設計 的時候了。本章以一個虛構的範例,串連前面所有主題,探索不同的設計考量,並提供一套 有效的設計流程,讓你能針對任何使用情境自行設計 API。
本章始終以 使用者體驗(User Experience) 為核心來錨定設計決策。現今的消費者習慣了優秀的產品體驗——不僅僅是能完成工作,還要符合需求與生活方式。這種對體驗品質的高期望也延伸到開發者使用 API 時的 開發者體驗(Developer Experience, DX)。
我們不是為自己設計 API,而是為 接收資料的系統 以及 建構這些系統的人 而設計。如果開發者無法使用我們提供的資料,那麼我們的設計就是失敗的。
本章使用兩個不同情境來探索如何以 使用者為中心的設計流程 開始,並在過程中獲取回饋。以下流程只是一個起點框架——最重要的是,它旨在以有益於 API 使用者的方式蒐集回饋。
如果你想一邊跟著範例練習,可以搭配附錄 A 的 API 設計工作表一起使用。
情境一(Scenario 1)#
以下是本章第一部分使用的虛構情境:
你是一家快速成長的圖片封存新創公司 MyFiles 的首席工程師。公司的主要產品讓個人用戶和企業能私密地封存資料。由於新用戶穩定成長且累積了大量封存中繼資料(metadata),你和團隊認為 建立並發布 API 有巨大潛力。身為首席工程師,你被交付在下一季推出新 API 的任務。
定義商業目標(Define Business Objectives)#
在開始撰寫程式碼或 API 規格之前,先問自己兩個問題:
- 你要解決什麼問題?——答案應該足夠精簡,可以放在任何產品或技術規格書的最頂端,並包含問題如何影響客戶與業務的資訊。
- 你希望產生什麼影響?——答案應定義 API 的成功標準,描繪你希望開發者使用 API 時展現的期望行為。
越早提出這些問題,就越能做出有根據的設計決策來達成目標。
MyFiles API 的問題與影響陳述
問題(Problem)
我們的封存儲存庫為直接客戶提供有價值的檔案中繼資料。客戶利用這些資料整合到業務關鍵服務中,但目前他們只能透過 下載 CSV 再上傳 CSV 到其他業務產品的方式來完成。我們目前沒有提供一種方式,讓使用第三方整合的客戶能以 程式化方式 存取封存的中繼資料。
影響(Impact)
建立 API 後,為封存中繼資料建構業務整合的開發者將能建立增強我們產品的外掛程式(plug-ins)。此外,現有客戶將能以過去無法實現的方式使用我們的產品,從而 提升每日的參與度。
在這些陳述中,提到了三個關鍵角色:
- MyFiles 業務本身
- 客戶
- 建構第三方整合的開發者
雖然 MyFiles API 涉及三方關係,但對其他業務而言,問題與影響陳述中可能只有公司與開發者(他們同時也是客戶)兩方。請利用問題與影響陳述來 清楚定義 API 使用者是誰。
定義完成後,確保公司其他 利害關係人(stakeholders) 達成共識。所有將建構或使用此 API 的相關方都需要理解它要解決的問題。不一致的期望 會導致後續衝突,進而產生不一致或矛盾的 API 設計。
清楚定義問題與影響的另一個重要原因是:在設計提案初期、尚未實作任何東西之前,可能會出現 意識形態式的爭論。例如,利害關係人可能對實作細節有強烈意見(如該用 RPC 還是 REST)。沒有明確的問題陳述,整家公司可能陷入意識形態的僵局。清晰的問題與影響陳述 有助於引導以使用者需求為基礎的 務實選擇。
記住,你的 API 會隨著產品變化而改變。在最好的情況下,API 會成為一個隨著與網際網路上其他實體互動而成長的「活系統」。今天視為理所當然的「事實」明天可能不再成立。但不要因此停止設計——為今天設計,同時為明天留一扇小門。
即使這不是你第一次為產品設計 API,定義問題與影響陳述仍然很重要——事實上 更加重要。已建立的 API 和產品因為向後相容性與其他團隊的依賴關係而更為複雜。先做好困難的前期工作,確保你已研究過:
- 目前有哪些 可用的 API,它們的模式與慣例是什麼?
- 你已發布的類似 API 中,最受歡迎的功能 是什麼?能否利用現有的埋點或日誌來取得支持數據?
- 你 最想改變 現有 API 的哪些部分,改變策略是什麼?
- 哪些 其他團隊 會受到新 API 影響,如何盡早取得他們的回饋?
列出關鍵使用者故事(Outline Key User Stories)#
在概述問題與期望影響之後,寫下你預期 API 將滿足的使用案例。如果你熟悉敏捷方法論(Agile),這些類似於 使用者故事(User Stories),其中「使用者」就是開發者。
使用者故事範本:
As a [user type], I want [action] so that [outcome].
身為 [使用者類型],我希望 [動作] 以便 [結果]。
MyFiles API 情境一的使用者故事範例
- 身為開發者,我希望 請求檔案清單,以便查看使用者上傳了哪些檔案。
- 身為開發者,我希望 請求單一檔案的詳細資訊,以便取得使用者已上傳檔案的細節。
- 身為開發者,我希望 代表使用者上傳檔案,讓使用者不需離開我的應用程式就能將檔案新增到 MyFiles。
- 身為開發者,我希望 代表使用者編輯檔案,讓使用者不需離開我的應用程式就能修改 MyFiles 中的檔案。
選擇技術架構(Select Technology Architecture)#
現代消費者不只尋求能完成工作的產品——產品設計延伸到了選擇和接收產品的整體體驗。IKEA 創造了包裝簡潔、無需說明書即可組裝的產品;Amazon 提供「無麻煩」的配送。期望無縫消費者體驗的開發者,對你的 API 也有同樣的期待。因此,選擇正確的範式與認證系統 非常重要。
以下是針對 MyFiles API 的各範式優缺點分析:
| 範式 | 優點 | 缺點 | 選用? |
|---|---|---|---|
| REST | MyFiles 本質上是 資源導向的——資源就是封存的內容與中繼資料。需要支援的操作是簡單的 CRUD 操作。 | REST 是對資源模型的長期承諾。若未來需支援多種動作,REST 可能不太適合。 | 是 |
| RPC | 可擴展至 CRUD 以外的其他動作。 | 目前 API 不需要 CRUD 以外的操作,因此沒有支援其他動作的必要。 | 否 |
| GraphQL | 對開發者靈活。容易保持 payload 精簡。 | 實作過於複雜。目前不需要客戶端呈現功能。 | 否 |
對 MyFiles API 而言,基於優缺點分析,REST 是最佳選項。REST 模式與 MyFiles 的資源導向產品緊密契合。
情境一聚焦於建構 REST API。在情境二中,將探討 WebHooks 如何提供將資料推送給開發者的機制,讓開發者不必持續輪詢(polling)REST API 或維持與 MyFiles 伺服器的長連接。
選好傳輸方式後,接下來選擇 認證機制。由於部分客戶有敏感檔案,我們需要比 Basic Authentication 更強健的方案,因此選擇了 OAuth 搭配短期 Token 與 Refresh Token,以確保使用者封存的私人檔案具有更高的安全等級。
選定 OAuth 後,需要挑選 OAuth Scopes。為此,先列出要提供的資源類型及 API 允許的操作:
| 資源或物件 | 操作 |
|---|---|
| 檔案及其中繼資料 | Create(建立) |
| 檔案及其中繼資料 | Read(讀取) |
| 檔案及其中繼資料 | Update(更新) |
| 檔案及其中繼資料 | Delete(刪除) |
針對 Scope 的設計,有幾種選項:
- 建立一個通用 scope
files,涵蓋所有檔案相關操作 - 將檔案操作分為兩組:讀取(reading) 和 寫入(writing)
- 更細粒度地將 scope 拆分為特定的 CRUD 操作
經過更仔細的考量,團隊決定 取消刪除檔案的功能——這不是使用者經常執行的操作,且被識別為風險最高的操作。因此 MyFiles 將採用簡單的 read 和 write scope 來涵蓋三種操作:
| 資源或物件 | 操作 | Scope |
|---|---|---|
| 檔案及其中繼資料 | Create | write |
| 檔案及其中繼資料 | Read | read |
| 檔案及其中繼資料 | Update | write |
在 MyFiles 範例中,列出所需操作並思考 OAuth Scope 的過程導致了一個重要決策——縮減初期 API 的功能範圍。設計過程中不可避免地需要做出關於實作與範圍的決策。只要這些決策有助於更好地解決最初定義的問題,就應該欣然接受。
撰寫 API 規格書(Write an API Specification)#
做完關鍵的高層決策後,接下來要撰寫 規格書(Specification, Spec)。規格書的價值在於:
- 讓你有機會 徹底思考設計
- 作為與其他人 溝通的產出物,特別是向利害關係人蒐集回饋時
- 達成共識後,作為一份 契約(contract),讓你能平行建構 API 實作的各個部分
建議使用具備 版本控制 和 留言功能 的協作文件編輯軟體,以提升參與度、追蹤回饋,並讓所有人掌握最新變更。
MyFiles API 技術規格書範例(Introduction)
Title
- Proposal: MyFiles API Spec
Authors
- Brenda Jin, Saurabh Sahni, Amir Shevat
Problem
- 我們的封存儲存庫為直接客戶提供有價值的檔案中繼資料。客戶利用這些資料整合到業務關鍵服務中,但目前只能透過下載/上傳 CSV 的方式完成。我們沒有提供讓使用第三方整合的客戶以程式化方式存取儲存庫中繼資料的途徑。
Solution
- 建立一個 API,讓開發者能以程式化方式存取 MyFiles 檔案。
Implementation
- 選用 REST,原因如下:
- REST 資源範式與 MyFiles 在技術堆疊中處理檔案的方式一致
- API 所需的檔案操作與 CRUD 操作高度吻合
- 目前不需要事件傳輸機制
Authentication
- 此 API 使用 OAuth 2.0,搭配 refresh token 與 token 到期機制。
Other things we considered
- WebHooks 是讓開發者獲取使用者事件(如檔案上傳、檔案變更)資訊的好方法,可避免持續輪詢。我們決定納入 WebHooks 的概要設計,預計作為 API 的第二階段建構。
- 決定暫不為第三方開發者實作 DELETE 操作,因為風險高且對初期 API 發布非必要。
規格書在概述之後,應深入到越來越詳細的資訊,例如:
- 開發者的 工作流程概述
- 認證資訊(若有建構新的授權機制)
- 資料傳輸協定的任何 相關細節
- 視覺化圖表——在描述文字難以表達的複雜資訊時特別有用
對於 REST 或 RPC API,表格 是詳述多維度面向的有效工具,可概述 URI 或方法名稱、輸入、輸出、錯誤與 scope。
MyFiles API URI 詳細規格表
| URI | 輸入 | 輸出 | Scope |
|---|---|---|---|
| GET /files | 選填:include_deleted(bool, 預設 false)、limit(int, 預設 100, 最大 1000)、cursor(string, 預設 null)、last_updated_after(timestamp, 預設 null) | 200 OK,回傳 $file 資源陣列,每個包含 id、name、date_added、last_updated、size、permalink、is_deleted | read |
| GET /files/:id | 無 | 200 OK,回傳 $file | read |
| PATCH /files/:id | 可更新欄位:name(string)、notes(string) | 202 Accepted,回傳 $file | write |
| POST /files/:id | 必填:name(string);選填:notes(string) | 200 Created,回傳 $file | write |
在表格中:
- URI(或 Endpoint)欄 應包含 REST 端點的 HTTP 方法
- 輸入欄 應包含所有輸入參數、可接受的型別、預設值,以及參數是否必填
- 輸出欄 應概述成功回應及其型別
- Scope 欄 指定授予存取該 API 方法的 OAuth scope
MyFiles API HTTP 錯誤狀態碼
| 狀態碼 | 說明 | 錯誤回應範例 |
|---|---|---|
| 200 OK | 請求成功。 | 見 URI 規格表的輸出 |
| 201 Created | 請求成功,新檔案已建立。 | 見 URI 規格表的輸出 |
| 202 Accepted | 檔案更新成功。 | 見 URI 規格表的輸出 |
| 400 Bad Request | 請求無法接受,通常因缺少參數或錯誤(如檔案過大)。 | {"error": "missing_parameter", "message": "..."} 或 {"error": "file_size_too_large", "message": "..."} |
| 401 Unauthorized | 未提供有效的 access token。 | {"error": "unauthorized", "message": "The provided token is not valid."} |
| 403 Forbidden | 使用者可能沒有查看該檔案的權限。 | {"error": "forbidden", "message": "You do not have permission to access the requested file <id>."} |
| 404 Not Found | 請求的檔案未找到。 | {"error": "file_not_found", "message": "The requested file <id> was not found."} |
| 429 Too Many Requests | 在指定時間內發送了過多請求。 | {"error": "too_many_requests", "message": "...Try again in <time> minutes."} |
| 500 Server Error | 伺服器端發生錯誤。 | — |
有時你需要空間來描述哪些型別是 可為 null 的(nullable)、哪些是 選填的(optional)。你可能還需要加入更多資訊,例如如何透過 API 處理檔案上傳。可以加一個「備註(Notes)」欄來放這些資訊。此外,規格書中也可以包含關於 擴展性、效能、日誌記錄與安全性 的額外資訊,並在結尾加入一個 開放問題(Open Questions) 區段。
情境二(Scenario 2)#
讓我們把 MyFiles 的 API 設計推進到下一步:
你已經建構並發布了 MyFiles REST API,過去幾個月取得了巨大成功。你持續與開發者保持聯繫以獲取回饋,聽到他們希望能在 檔案變更時接收更新通知。以原本的 REST 設計,開發者必須對每個重要檔案發出多次請求,檢查是否有任何差異。這種 輪詢行為(polling) 對你的基礎設施造成負擔,對開發者也不友善。因此,你的團隊決定建構一些東西來解決這個問題。
定義問題(Define the Problem)#
情境二的問題與影響陳述
問題(Problem)
我們的 REST API 讓開發者能以程式化方式存取第三方整合。然而,開發者目前追蹤檔案變更的唯一方式是 持續輪詢 API,每個檔案最多每分鐘一次。
影響(Impact)
在為 API 新增額外功能後,開發者將能在他們關心的 檔案發生變更時接收更新通知。
列出關鍵使用者故事#
身為開發者,我希望在檔案被 新增、變更或移除 時接收更新,以便不需持續輪詢 REST API。
選擇技術架構#
在情境一中選擇了 REST API,在這個情境中則考慮多種 事件驅動 API(Event-driven API)。以下是三種常見模式的優缺點分析:
| 範式 | 優點 | 缺點 | 選用? |
|---|---|---|---|
| WebHooks | MyFiles 開發者可能希望在檔案被新增、移除或變更時接收事件。 | 若檔案變更過於頻繁,可能需要精密的基礎設施來去重複事件,避免無意間對開發者的應用程式發動 DDoS 攻擊。 | 是 |
| WebSockets | 可被內部客戶端用於 UI 顯示。 | 不希望開發者透過 API 為 MyFiles 建構 UI 客戶端。且不認為有支援長連接的使用場景。 | 否 |
| HTTP Streaming | 適合頻繁推送資料。 | 以每個檔案的低變更頻率來看,不需要 HTTP Streaming。 | 否 |
基於分析,選擇 WebHooks。
設計時還需考慮開發者將如何 設定偏好,選擇他們感興趣的事件和檔案。本章不深入探討配置設計。
撰寫 API 規格書#
MyFiles WebHooks 設計規格書範例(情境二)
Title
- Proposal: MyFiles API WebHooks Spec
Authors
- Brenda Jin, Saurabh Sahni, Amir Shevat
Problem
- REST API 讓開發者能以程式化方式存取第三方整合。然而,開發者追蹤檔案變更的唯一方式是持續輪詢 API,每個檔案最多每分鐘一次。
Solution
- 建立事件驅動的 WebHooks API,讓開發者能接收與 MyFiles 檔案相關的選定新增、更新與變更事件。
Implementation
- 開發者指定一個由他們控制的 endpoint,供 WebHooks 針對每個規格發送帶有 JSON body 的 POST 請求。
Authentication
- 此 API 使用 MyFiles REST API 的 OAuth 2.0 認證。任何已安裝
readOAuth scope 的開發者都能接收 WebHook 請求。
Other things we considered
- 設計事件驅動 API 有多種方式,我們選擇了 WebHooks。也考慮過 WebSockets 和長連接 HTTP Streaming。
MyFiles WebHooks 事件物件規格
| 事件 | Payload | OAuth Scope |
|---|---|---|
| file_added | {"id": $id, "resource_type": "file", "event_type": "added", "name": string, "date_added": $timestamp, "last_updated": $timestamp, "size": int, "permalink": $uri, "notes": array<file_notes>, "uri": $uri} | read |
| file_changed | {"id": $id, "resource_type": "file", "event_type": "changed", "name": string, "date_added": $timestamp, "last_updated": $timestamp, "size": int, "permalink": $uri, "notes": array<file_notes>, "uri": $uri} | read |
| file_removed | {"id": $id, "resource_type": "file", "event_type": "removed", "name": string, "date_added": $timestamp, "last_updated": $timestamp, "size": null, "permalink": null, "notes": null, "uri": null} | read |
注意 file_removed 事件中,size、permalink、notes、uri 欄位為 null。
設計時有一個有趣的決策:事件名稱是否應更細粒度,payload 中僅包含引用(讓開發者透過 payload.uri 欄位發出後續請求獲取更多資訊),還是在 payload 中包含更多資料?
驗證你的決策(Validate Your Decisions)#
撰寫完每份規格書後,應找到方法向利害關係人 測試你的想法。不要等到 API 完成後才尋求意見——及早取得正確回饋 能省下大量時間,避免昂貴的重寫。
與利害關係人審查規格書#
本章描述的設計方法論的指導原則之一是 盡早且頻繁地啟用回饋。規格書正是蒐集回饋的起點,你可以取得關於問題、高層解決方案和細節的意見。
蒐集回饋時的關鍵原則:
- 與他人審查規格書並 主動邀請回饋
- 找到可能建構業務整合的開發者,詢問他們的看法
- 若利害關係人是內部人員,確保取得他們的意見
- 在撰寫任何程式碼之前 經歷這些回饋循環——建立與使用 API 的思維模式不同,收到的意見能幫助你在開始建構前修正可用性問題
蒐集回饋的目的 不只是「取得簽核」。你應該邀請 建設性的異議與批評,這不是要讓他人推翻你的設計,而是蒐集有價值的資訊以在建構前改進設計。
蒐集回饋也不是要證明(或反駁)你的設計能力。它應該是對你在本章開頭定義的問題的 可能解決方案的真誠探索。保持真正的好奇心,能促進想法交流。蒐集回饋時,你應該加深對其他利害關係人顧慮與猶豫的理解——即使這些意見難以接受,即使你不同意。
在措辭問題時,具體的問題 比籠統的問題更有幫助:
- 不太有用:「你喜歡嗎?」
- 更有幫助:「你在實作 WebHooks API 時遇到了哪些問題?」
你最終可能不會採納某些利害關係人的建議,因為你的解決方案已經以另一種方式回應了他們的顧慮。但有了更深入的理解,你的解決方案將更 全面、更多元,並能做出更 有意識的權衡取捨。
綜合複雜的想法、融合看似不同的利益——這比單純採用第一個解決方案需要更多努力。但 建構對的 API 遠比建構錯的 API 重要。
使用模擬資料進行互動式使用者測試#
建議使用任何可用工具來測試設計並蒐集回饋。模擬資料(Mock Data) 是其中一項有用工具:
- 為開發者建立一個介面,讓他們能以你提議的規格格式取得模擬資料
- 模擬資料是一組 固定回應,可透過應用程式或其他輕量級介面提供
- 模擬資料應用程式將成為利害關係人的 更互動式的測試環境,幫助你在完整實作 API 前獲得更具體的回饋
- 模擬工具也能讓你的開發者 與 API 開發同步進行 應用程式和整合的實作
案例:Macys.com 響應式結帳系統
2015 年,Macys.com 建構了全新的響應式結帳系統,使用一系列 JSON API。先前的系統以 Java 後端搭配 Spring Framework 建構為獨立頁面。
由於後端開發者需要數週時間來建立 JSON API,團隊首先 達成 API 規格共識。接著,前端開發者建立了一個輕量的 Node.js 模擬應用程式,在 API 開發期間提供假的固定回應。這讓互動式的響應前端體驗能與 API 開發 平行建構和測試。
當結帳體驗準備上線給客戶時,團隊只需將 Node.js 模擬應用程式 替換為新的 API 端點 即可。
Beta 測試者#
利害關係人通常不只有內部人員。如果你有 面向公眾的 API,在蒐集回饋時也需考慮外部利害關係人。
在基於規格書做出決策並開始建構 API 後,考慮從開發者合作夥伴處取得回饋——透過 Beta 測試計畫:
- 讓開發者合作夥伴 提前存取 新 API
- 他們在正式公開發布前提供回饋
- 這給了 API 建構者額外的機會,針對 真實使用者與真實使用案例 改進 API 設計
- 更多關於建立開發者合作夥伴計畫的內容,請見第十章
結語(Closing Thoughts)#
本章提供了一個 通用範本,供你用來設計 API。在思考如何為自己的 API 客製化設計流程時,請考慮以下原則:
- 讓流程盡可能 輕量且快速
- 同時 最大化回饋 的收集
本書持續鼓勵你思考計算中的 人的因素。一次又一次,我們看到當 API 提供者忽視使用者或開發者社群的需求時,就會做出糟糕的設計決策。請根據你的組織需求調整本章提供的範本,但 不要忘記使用者!