pyramid

image by stable difussion, prompt by alswl

這個問題困擾了我很長時間,始于我求學時期,每一次都需要與團隊成員進行交流和討論。 從最初的自由風格到后來的 REST,我經常向項目組引用?Github v3[1]?和 Foursqure API(已經無法訪問,暴露年齡) 文檔。 然而,在實踐過程中,仍然會有一些與實際工作或公司通用規范不匹配的情況, 這時候我需要做一些補充工作。最終,我會撰寫一個簡要的?DEVELOPMENT.md?文檔,以描述設計方案。

但我對該文檔一直有更多的想法,它還不夠完善。因此,我想整理出一份簡單(Simple)而實用(Pragmatic)Web API 最佳實踐,也就是本文。

為什么我們需要 API 統一規范

這個問題似乎很明顯,但是深入剖析涉及團隊協作效率和工程設計哲學。

API(Application Programming Interface,應用程序編程接口)是不同軟件系統之間交互的橋梁。在不同軟件系統之間進行通信時, API 可以通過標準化的方式進行數據傳輸和處理,從而實現各種應用程序的集成。

當我們開始撰寫 API 文檔時,就會出現一個范式(Design Pattern),這是顯式還是隱式的, 是每個人一套還是公用同一套。這就像我們使用統一的 USB 接口一樣,統一降低了成本,避免了可能存在的錯誤。具體來說,這有以下幾個原因:

why

image by alswl

雖然使用統一規范確實有一些成本,需要框架性的了解和推廣,但我相信在大部分場景下, 統一規范所帶來的收益遠遠高于這些成本。

然而,并非所有的情況下都需要考慮 API 規范。對于一些短生命周期的項目、影響面非常小的內部項目和產品, 可能并不需要過多關注規范。 此外,在一些特殊的業務場景下, 協議底層可能會發生變化,這時候既有的規范可能不再適用。但即使如此,我仍然建議重新起草新的規范,而不是放棄規范不顧。

規范的原則

在制定 API 規范時,我們應該遵循一些基本原則,以應對技術上的分歧,我總結了三個獲得廣泛認可的原則:

principle

image by alswl

REST 到底行不行?

Web API 領域,RESTful API[2]?已經成為廣受歡迎的協議。 其廣泛適用性和受眾范圍之廣源于其與 HTTP 協議的綁定,這使得 RESTful API 能夠輕松地與現有的 Web 技術進行交互。如果您對 REST 不熟悉, 可以查看?阮一峰的 RESTful API 設計指南[3]?以及?RESTful API 設計最佳實踐[4]。

REST 是一種成熟度較高的協議,Leonard Richardson[5]?將其描述為四種成熟度級別:

rest-four-level

image by alswl

  1. 1. The Swamp of POX,使用 HTTP 承載 Legacy 協議(XML)
  2. 2. Resources:使用資源抽象
  3. 3. HTTP Verbs:使用豐富的 HTTP Verbs
  4. 4.?Hypermedia Controls:使用?rel?鏈接進行 API 資源整合,JSON:API[6]?是登峰造極的表現

REST 的核心優勢在于:

然而,REST 并非一種具體的協議或規范,而是一種風格理念。盡管 REST 定義了一些規則和原則,如資源的標識、統一接口、無狀態通信等, 但它并沒有規定一種具體的實現方式。因此,在實際開發中,不同的團隊可能會有不同的理解和實踐, 從而導致 API 的不一致性和可維護性降低。

此外,REST 也有一些局限性和缺陷:

因此,雖然 REST 風格是一個不錯的指導思想,但在具體實現時需要結合具體業務需求和技術特點,有所取舍,才能實現良好的 API 設計。 最后,我們是否需要 Web API 設計規范,遵循 REST 風格呢?我認為 REST 能夠解決 90% 的問題,但還有 10% 需要明確規定細節。

Web API 規范的選擇題

因為我們的協議基于 HTTP 和 REST 設計,我們將以 HTTP 請求的四個核心部分為基礎展 開討論,這些部分分別是:URL、Header、Request 和 Response。

URL 最佳實踐

我的 URL 設計啟蒙來自于?Ruby on Rails[7]。 在此之前,我總是本能地將模型信息放到 URL 之上,但實際上良好的 URL 設計應該是針對系統信息結構的規劃。 因此,URL 設計不僅僅要考慮 API,還要考慮面向用戶的 Web URL。

為了達到良好的 URL 設計,我總結了以下幾個規則:

通常情況下,URL 的模型如下所示:

/$(prefix)/$(module)/$(model)/$(sub-model)/$(verb)?$(query)#${fragment}

其中,Prefix 可能是 API 的版本,也可能是特殊限定,如有些公司會靠此進行接入層分流; Module 是業務模塊,也可以省略;Model 是模型;SubModel 是子模型,可以省略; Verb 是動詞,也可以省略;Query 是請求參數;Fragment 是 HTTP 原語 Fragment。

需要注意的是,并非所有的組成部分都是必須出現的。例如,SubModel 和 Verb 等字段可 以在不同的 URL 風格中被允許隱藏。

設計風格選擇

注:請注意,方案 A / B / C 之間沒有關聯,每行上下也沒有關聯

問題解釋(見下方單列分析)方案 A方案 B方案 C
API Path 里面 Prefix/apis/api二級域名
Path 里面是否包含 API 版本版本在 URL 的優勢???
Path 是否包含 Group???
Path 是否包含動作HTTP Verb 不夠用的情況??? (純 REST)看情況(如果 HTTP Verb CRUD 無法滿足就包含)
模型 ID 形式Readable Stable Identity 解釋自增 IDGUIDReadable Stable ID
URL 中模型單數還是復數單數復數列表復數,單向單數
資源是一級(平鋪)還是多級(嵌套)一級和多級的解釋一級(平鋪)多級(嵌套)
搜索如何實現,獨立接口(/models/search)還是基于列表/models/ 接口獨立合并
是否有 Alias URLAlias URL 解釋???
URL 中模型是否允許縮寫(或精簡)模型縮寫解釋???
URL 中模型多個詞語拼接的連字符-_Camel
是否要區分 Web API 以及 Open API(面向非瀏覽器)???

版本在 URL 的優勢

我們在設計 URL 時遵循一致性的原則,無論是哪種身份或狀態,都會使用相同的 URL 來訪問同一個資源。 這也是 Uniform Resource Location 的基本原則。雖然我們可以接受不同的內容格式(例如 JSON / YAML / HTML / PDF / etc), 但是我們希望資源的位置是唯一的。

然而,問題是,對于同一資源在不同版本之間的呈現,是否應該在 URL 中體現呢?這取決于設計者是否認為版本化屬于位置信息的范疇。

根據 RFC 的設計,除了 URL 還有 URN(Uniform Resource Name)[8], 后者是用來標識資源的,而 URL 則指向資源地址。實際上,URN 沒有得到廣泛的使用,以至于 URI 幾乎等同于 URL。

HTTP Verb 不夠用的情況

REST 設計中,我們需要使用 HTTP 的 GET / POST / PUT / DELETE / PATCH / HEAD 等動詞對資源進行操作。 比如使用 API?GET /apis/books?查看書籍列別,這個自然且合理。 但是,當需要執行類似「借一本書」這樣的動作時, 我們沒有合適的動詞(BORROW)來表示。針對這種情況,有兩種可行的選擇:

  1. 1. 使用 POST 方法與自定義動詞,例如 POST /apis/books/borrow,表示借書這一動作;
  2. 2. 創建一個借書記錄,使用資源新增方式來結構不存在的動作,例如 POST /apis/books/borrow-log/;

這個問題在復雜的場景中會經常出現,例如用戶登錄(POST /api/auth/login vs POST /api/session)和帳戶轉賬(vs 轉賬記錄創建)等等。 API 抽象還是具體,始終離不開業務的解釋。我們不能簡單地將所有業務都籠統概括到 CRUD 上面, 而是需要合理劃分業務,以便更清晰地實現和讓用戶理解。

在進行設計時,我們可以考慮是否需要為每個 API 創建一個對應的按鈕來方便用戶的操作。 如果系統中只有一個名為?/api/do?的 API 并將所有業務都綁定在其中,雖然技術上可行, 但這種設計不符合業務需求,每一層的抽象都是為了標準化解決特定問題的解法,TCP L7 設計就是這種理念的體現。

Readable Stable Identity 解釋

在標記一個資源時,我們通常有幾種選擇:

我個人有一個設計小技巧:使用?${type}/${type-id}?形式的 slug 來描述標識符。Slug 是一種人類可讀的唯一標識符, 例如?hostname/abc.sqa?或?ip/172.133.2.1。 這種設計方式可以在可讀性和唯一性之間實現很好的平衡。

A slug is a human-readable, unique identifier, used to identify a resource instead of a less human-readable identifier like an id .

from What’s a slug. and why would I use one? | by Dave Sag[9]

PS:文章最末我還會介紹一套 Apple Music 方案,這個方案兼顧了 ID / Readable / Stable 的特性。

一級和多級的解釋

URL 的層級設計可以根據建模來進行,也可以采用直接單層結構的設計。具體問題的解決方式, 例如在設計用戶擁有的書籍時,可以選擇多級結構的 /api/users/foo/books 或一級結構的 /api/books?owner=foo。

技術上這兩種方案都可以,前者尊重模型的歸屬關系,后者則是注重 URL 結構的簡單。

多級結構更直觀,但也需要解決可能存在的多種組織方式的問題,例如圖書館中書籍按照作者或類別進行組織? 這種情況下,可以考慮在多級結構中明確模型的歸屬關系, 例如 /api/author/foo/books(基于作者)或 /api/category/computer/books(基于類別)。

Alias URL 解釋

對于一些頻繁使用的 URL,雖然可以按照 URL 規則進行設計,但我們仍然可以設計出一個更為簡潔的 URL, 以方便用戶的展示和使用。這種設計在 Web URL 中尤其常見。比如一個圖書館最熱門書籍的 API

# 原始 URL
https://test.com/apis/v3/books?sort=hot&limit=10

# Alias URL
https://test.com/apis/v3/books/hot

模型縮寫解釋

通常,在對資源進行建模時,會使用較長的名稱來命名,例如書籍索引可能被命名為?BookIndex?,而不是?Index。 在 URL 中呈現時,由于?/book/book-index?的 URL 前綴包含了 Book,我們可以減少一層描述, 使 URL 更為簡潔,例如使用?/book/index。這種技巧在 Web URL 設計中非常常見。

此外,還有一種模型縮寫的策略,即提供一套完整的別名注冊方案。別名是全局唯一的, 例如在 Kubernetes 中,?Deployment[10]?是一種常見的命名,而?apps/v1/Deployment?是通過添加 Group 限定來表示完整的名稱, 同時還有一個簡寫為?deploy。這個機制依賴于 Kubernetes 的 API Schema 系統進行注冊和工作。

Header 最佳實踐

我們常常會忽略 Header 的重要性。實際上,HTTP 動詞的選擇、HTTP 狀態碼以及各種身 份驗證邏輯(例如 Cookie / Basic Auth / Berear Token)都依賴于 Header 的設計。

設計風格選擇

問題解釋(見下方單列分析)方案 A方案 B方案 C
是否所有 Verb 都使用 POST關于全盤 POST???
修改(Modify)動作是 POST 還是 PATCH?POSTPATCH
HTTP Status 返回值2XX 家族充分利用 HTTP Status只用核心狀態(200 404 302 等)只用 200
是否使用考慮限流系統? 429??
是否使用緩存系統? ETag / Last Modify??
是否校驗 UserAgent???
是否校驗 Referrral???

關于全盤 POST

有些新手(或者自認為有經驗的人)可能得出一個錯誤的結論,即除了 GET 請求以外, 所有的 HTTP 請求都應該使用 POST 方法。甚至有些人要求 所有行為(即使是只讀的請求)也應該使用 POST 方法[11]。 這種觀點通常會以“簡單一致”、“避免緩存”或者“運營商的要求”為由來支持。

然而,我們必須明白 HTTP 方法的設計初衷:它是用來描述資源操作類型的,從而派生出了包括緩存、安全、冪等性等一系列問題。 在相對簡單的場景下,省略掉這一層抽象的確不會帶來太大的問題,但一旦進入到復雜的領域中, 使用 HTTP 方法這一層抽象就顯得非常重要了。這是否遵循標準將決定你是否能夠獲得標準化帶來的好處, 類比一下就像一個新的手機廠商可以選擇不使用 USB TypeC 接口。 技術上來說是可行的,但同時也失去了很多標準化支持和大家心智上的約定俗成。

我特別喜歡一位 知乎網友[12] 的 評論[13]:「路由沒有消失,只是轉移了」。

2XX 家族

HTTP 狀態碼的用途在于表明客戶端與服務器間通信的結果。2XX 狀態碼系列代表服務器已經成功接收、 理解并處理了客戶端請求,回應的內容是成功的。以下是 2XX 系列中常見的狀態碼及其含義:

2XX 系列的狀態碼表示請求已被成功處理,這些狀態碼可以讓客戶端明確知曉請求已被正確處理,從而進行下一步操作。

是否需要全面使用 2XX 系列的狀態碼,取決于是否需要向客戶端明確/顯示的信息, 告知它下一步動作。如果已經通過其他方式(包括文檔、口頭協議)描述清楚, 那么確實可以通盤使用 200 狀態碼進行返回。但基于行為傳遞含義, 或是基于文檔(甚至口頭協議)傳遞含義,哪種更優秀呢?是更為復雜還是更為簡潔?

Request 最佳實踐

設計風格選擇

問題解釋(見下方單列分析)方案 A方案 B方案 C
復雜的參數是放到 Form Fields 還是單獨一個 JSON BodyForm FieldsBody
子資源是一次性查詢還是獨立查詢嵌套獨立查詢
分頁參數存放HeaderURL Query
分頁方式分頁方式解釋Page basedOffset basedContinuation token
分頁控制者分頁控制著解釋客戶端服務端

分頁方式解釋

我們最為常見的兩種分頁方式是 Page-based 和 Offset-based,可以通過公式進行映射。 此外,還存在一種稱為 Continuation Token 的方式,其技術類似于 Oracle 的 rownum 分頁方案[14],使用參數 start-from=? 進行描述。 雖然 Continuation Token 的優缺點都十分突出,使用此種方式可以將順序性用于替代隨機性。

分頁控制著解釋

在某些情況下,我們需要區分客戶端分頁(Client Pagination)和服務器分頁(Server Pagniation)。 客戶端分頁是指下一頁的參數由客戶端計算而來,而服務器分頁則是由服務器返回?rel?或 JSON.API 等協議。 使用服務器分頁可以避免一些問題,例如批量屏蔽了一些內容,如果使用客戶端分頁,可能會導致缺頁或者白屏。

Response 最佳實踐

設計風格選擇

問題解釋(見下方單列分析)方案 A方案 B方案 C
模型呈現種類模型的幾種形式單一模型多種模型
大模型如何包含子模型模型模型的連接、側載和嵌入嵌入核心模型 + 多次關聯資源查詢鏈接
字段返回是按需還是歸并還是統一統一使用 fields 字段按需
字段表現格式SnakeCamel
錯誤碼無自定,使用 Message自定義
錯誤格式全局統一按需
時區UTCLocalLocal + TZ
HATEOAS???

模型的幾種形式

API 設計中,對于模型的表現形式有多種定義。雖然這并不是 API 規范必須討論的話題,但它對于 API 設計來說是非常重要的。

我將模型常說的模型呈現方式分為一下幾類,這并非是專業的界定,借用了 Java 語境下面的一些定義。 這些名稱在不同公司甚至不同團隊會有不一樣的叫法:

models

image by alswl

除此之外,還經常使用兩類:Rich Model 和 Tiny Model(請忽略命名,不同團隊叫法差異比較大):

模型的連接、側載和嵌入

API 設計中,我們經常需要處理一個模型中包含多個子模型的情況,例如 Book 包含 Comments。 對于這種情況,通常有三種表現形式可供選擇:鏈接(Link)、側載(Side)和嵌入(Embed)。

models-with-children

image by alswl

鏈接(有時候這個 URL 也會隱藏,基于客戶端和服務端的隱式協議進行請求):

{
"data": {
"id": 42,
"name": "朝花夕拾",
"relationships": {
"comments": "http://www.domain.com/book/42/comments",
"author": [
"http://www.domain.com/author/魯迅"
]
}
}
}

側載:

{
"data": {
"id": 42,
"name": "朝花夕拾",
"relationships": {
"comments": "http://www.domain.com/book/42/comments",
"authors": [
"http://www.domain.com/author/魯迅"
]
}
},
"includes": {
"comments": [
{
"id": 91,
"author": "匿名",
"content": "非常棒"
}
],
"authors": [
{
"name": "魯迅",
"description": "魯迅原名周樹人"
}
]
}
}

嵌入:

{
"data": {
"id": 42,
"name": "朝花夕拾",
"comments": [
{
"id": 91,
"author": "匿名",
"content": "非常棒"
}
],
"authors": [
{
"name": "魯迅",
"description": "魯迅原名周樹人"
}
]
}
}

其他

還有一些問題沒有收斂在四要素里面,但是我們在工程實踐中也經常遇到,我將其捋出來:

我不是 HTTP 協議,怎么辦?

Web API 中較少遇到非 HTTP 協議,新建一套協議的成本太高了。在某些特定領域會引入一些協議, 比如 IoT 領域的?MQTT[15]

此外,RPC 是一個涉及廣泛領域的概念,其內容遠遠不止于協議層面。 通常我們會將 HTTP 和 RPC 的傳輸協議以及序列化協議進行對比。 我認為,本文中的許多討論也對 RPC 領域具有重要意義。

有些團隊或個人計劃使用自己創建的協議,但我的觀點是應盡量避免自建協議,因為真正需要創建協議的情況非常罕見。 如果確實存在強烈的需要,那么我會問兩個問題:是否通讀過 HTTP RFC 文檔和 HTTP/2 RFC 文檔?

我不是遠程服務(RPC / HTTP 等),而是 SDK 怎么辦?

本文主要討論的是 Web API(HTTP)的設計規范,并且其中一些規則可以借鑒到 RPC 系統中。 然而,討論的基礎都是建立在遠程服務(Remote Service)的基礎之上的。 如果你是 SDK 開發人員,你會有兩個角色,可能會作為客戶端和遠程服務器進行通信, 同時還會作為 SDK 提供面向開發人員的接口。對于后者,以下幾個規范可以作為參考:

后者可以參考一下這么幾個規范:

認證鑒權方案

一般而言,Web API 設計中會明確描述所采用的認證和鑒權系統。 需要注意區分「認證」和「鑒權」兩個概念。關于「認證」這一話題,可以在單獨的章節中進行討論,因此本文不會展開這一方面的內容。

Web API 設計中,常見的認證方式包括:HTTP Basic Auth、OAuth2 和賬號密碼登錄等。 常用的狀態管理方式則有 Bearer Token 和 Cookie。此外,在防篡改等方面,還會采用基于 HMac 算法的防重放和篡改方案。

忽略掉的話題

在本次討論中,我未涉及以下話題:異步協議(Web Socket / Long Pulling / 輪訓)、CORS、以及安全問題。 雖然這些話題重要,但是在本文中不予展開。

什么時候打破規則

有些開發者認為規則就是為了打破而存在的?,F實往往非常復雜,我們難以討論清楚各個細節。 如果開發者覺得規則不符合實際需求,有兩種處理方式:修改規則或打破規則。 然而,我更傾向于討論和更新規則,明確規范不足之處,確定是否存在特殊情況。 如果確實需要創建特例,一定要在文檔中詳細描述,告知接任者和消費者這是一個特例,說明特例產生的原因以及特例是如何應對的。

一張風格 Checklist

Github 風格

Github 的 API 是我常常參考的對象。它對其業務領域建模非常清晰,提供了詳盡的文檔,使得溝通成本大大降低。 我主要參考以下兩個鏈接: API 定義?GitHub REST API documentation[18]?和 面向應用程序提供的 API 列表?Endpoints available for GitHub Apps[19]?,該列表幾乎包含了 Github 的全部 API。

問題選擇備注
URL
API Path 里面 Prefix二級域名https://api.github.com
Path 里面是否包含 API 版本??Header X-GitHub-Api-Version API Versions[20]
Path 是否包含 Group??
Path 是否包含動作看情況(如果 HTTP Verb CRUD 無法滿足就包含)比如 PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge POST /repos/{owner}/{repo}/releases/generate-notes
模型 ID 形式Readable Stable Identity
URL 中模型單數還是復數復數
資源是一級(平鋪)還是多級(嵌套)多級
搜索如何實現,獨立接口(/models/search)還是基于列表/models/ 接口獨立
是否有 Alias URL?
URL 中模型是否允許縮寫(或精簡)??沒有看到明顯信息,基于多級模型也不需要,但是存在 GET /orgs/{org}/actions/required_workflows
URL 中模型多個詞語拼接的連字符- 和 _GET /repos/{owner}/{repo}/git/matching-refs/{ref} vs GET /orgs/{org}/actions/required_workflows
是否要區分 Web API 以及 Open API(面向非瀏覽器)??
Header
是否所有 Verb 都使用 POST??
修改(Modify)動作是 POST 還是 PATCH?PATCH
HTTP Status 返回值充分利用 HTTP Status常用,包括限流洗損
是否使用考慮限流系統? 429
是否使用緩存系統? ETag / Last ModifyResources in the REST API#client-errors[21]
是否校驗 UserAgent?
是否校驗 Referrral??
Request
復雜的參數是放到 Form Fields 還是單獨一個 JSON BodyBody參考 Pulls#create-a-pull-request[22]
子資源是一次性查詢還是獨立查詢嵌套從 Pulls 進行判斷
分頁參數存放URL Query
分頁方式PageUsing pagination in the REST API[23]
分頁控制者服務端同上
Response
模型呈現種類多種模型比如 Commits 里面的 明細和 Parent Commits[24]
大模型如何包含子模型模型核心模型 + 多次關聯資源查詢?沒有明確說明,根據幾個核心 API 反推
字段返回是按需還是歸并還是統一統一
字段表現格式Snake
錯誤碼Resources in the REST API#client-errors[25]
錯誤格式全局統一Resources in the REST API#client-errors[26]
時區復合方案(ISO 8601 > Time-Zone Header > User Last > UTC)Resources in the REST API#Timezones[27]
HATEOAS??

Azure 風格

Azure 的 API 設計遵循?api-guidelines/Guidelines.md at master · microsoft/api-guidelines[28], 這篇文章偏原理性,另外還有一份實用指導手冊在?Best practices in cloud applications[29]?和?Web API design best practices[30]。

需要注意的是,Azure 的產品線遠比 Github 豐富,一些 API 也沒有遵循 Azure 自己的規范。 在找實例時候,我主要參考?REST API Browser[31],?Azure Storage REST API Reference[32]。 如果具體實現和 Guidelines.md 沖突,我會采用 Guidelines.md 結論。

問題選擇備注
URL
API Path 里面 Prefix二級域名
Path 里面是否包含 API 版本??x-ms-version
Path 是否包含 Group?
Path 是否包含動作???沒有明確說明,但是有傾向使用 comp 參數來進行動作,保持 URL 的 RESTful 參考 Lease Container (REST API) – Azure Storage[33]
模型 ID 形式Readable Stable IdentityGuidelines.md#73-canonical-identifier[34]
URL 中模型單數還是復數復數Guidelines.md#93-collection-url-patterns[35]
資源是一級(平鋪)還是多級(嵌套)多級 / 一級api-design#define-api-operations-in-terms-of-http-methods[36],注 MS 有 comp=? 這種參數,用來處理特別的命令
搜索如何實現,獨立接口(/models/search)還是基于列表/models/ 接口?傾向于基于列表,因為大量使用 comp= 這個 URL Param 來進行子命令,比如 Incremental Copy Blob (REST API) – Azure Storage[37]
是否有 Alias URL?
URL 中模型是否允許縮寫(或精簡)?
URL 中模型多個詞語拼接的連字符CamelJob Runs – List – REST API (Azure Storage Mover)[38]
是否要區分 Web API 以及 Open API(面向非瀏覽器)??
Header
是否所有 Verb 都使用 POST??
修改(Modify)動作是 POST 還是 PATCH?PATCHAgents – Update – REST API (Azure Storage Mover)[39]
HTTP Status 返回值充分利用 HTTP StatusGuidelines.md#711-http-status-codes[40]
是否使用考慮限流系統?
是否使用緩存系統?Guidelines.md#75-standard-request-headers[41]
是否校驗 UserAgent??
是否校驗 Referrral??
Request
復雜的參數是放到 Form Fields 還是單獨一個 JSON BodyBody參考 Agents – Create Or Update – REST API (Azure Storage Mover)[42]
子資源是一次性查詢還是獨立查詢?
分頁參數存放?沒有結論
分頁方式Page based
分頁控制者服務端Agents – List – REST API (Azure Storage Mover)[43]
Response
模型呈現種類單一模型推測
大模型如何包含子模型模型?場景過于復雜,沒有單一結論
字段返回是按需還是歸并還是統一?
字段表現格式Camel
錯誤碼使用自定錯誤碼清單至少在各自產品內
錯誤格式自定義
時區?
HATEOAS?api-design#use-hateoas-to-enable-navigation-to-related-resources[44]

Azure 的整體設計風格要比 Github API 更復雜,同一個產品的也有多個版本的差異,看 上去統一性要更差一些。這種復雜場景想用單一的規范約束所有團隊的確也是更困難的。 我們可以看到 Azaure 團隊在 Guidelines 上面努力,他們最近正在推出 vNext 規范。

我個人風格

我個人風格基本繼承自 Github API 風格,做了一些微調,更適合中小型產品開發。 我的改動原因都在備注中解釋,改動出發點是:簡化 / 減少歧義 / 考慮實際成本。如果備注里面標記了「注」,則是遵循 Github 方案并添加一些觀點。

問題選擇備注
URL
API Path 里面 Prefix/apis我們往往只有一個系統,一個域名要承載 API 和 Web Page
Path 里面是否包含 API 版本?
Path 是否包含 Group?做一層業務模塊拆分,隔離一定合作邊界
Path 是否包含動作看情況(如果 HTTP Verb CRUD 無法滿足就包含)
模型 ID 形式Readable Stable Identity
URL 中模型單數還是復數復數
資源是一級(平鋪)還是多級(嵌套)多級 + 一級注:80% 情況都是遵循模型的歸屬,少量情況(常見在搜索)使用一級
搜索如何實現,獨立接口(/models/search)還是基于列表/models/ 接口統一 > 獨立低成本實現一些(早期 Github Issue 也是沒有 /search 接口
是否有 Alias URL??簡單點
URL 中模型是否允許縮寫(或精簡)?一旦做了精簡,需要在術語表標記出來
URL 中模型多個詞語拼接的連字符-
是否要區分 Web API 以及 Open API(面向非瀏覽器)??
Header
是否所有 Verb 都使用 POST??
修改(Modify)動作是 POST 還是 PATCH?PATCH
HTTP Status 返回值充分利用 HTTP Status
是否使用考慮限流系統? 429
是否使用緩存系統??簡單一些,使用動態數據,去除緩存能力
是否校驗 UserAgent?
是否校驗 Referrral??
Request
復雜的參數是放到 Form Fields 還是單獨一個 JSON BodyBody
子資源是一次性查詢還是獨立查詢嵌套
分頁參數存放URL Query
分頁方式Page
分頁控制者客戶端降低服務端成本,容忍極端情況空白
Response
模型呈現種類多種模型使用的 BO / VO / Tiny / Rich
大模型如何包含子模型模型核心模型 + 多次關聯資源查詢
字段返回是按需還是歸并還是統一統一Tiny Model(可選) / Model(默認) / Rich Model(可選)
字段表現格式Snake
錯誤碼注:很多場景只要 message
錯誤格式全局統一
時區ISO 8601只使用一種格式,不再支持多種方案
HATEOAS??

題外話 – Apple Music 的一個有趣設計

Apple Music

image from Apple Music

我最近在使用 Apple Music 時注意到了其 Web 頁面的 URL 結構:

/cn/album/we-sing-we-dance-we-steal-things/277635758?l=en

仔細看這個 URL 結構,可以發現其中 Path 包含了人類可讀的 slug,分為三個部分:alumn/$(name)/$(id) (其中包含了 ID)。 我立即想到了一個問題:中間的可讀名稱是否無機器意義,純粹面向自然人? 于是我測試了一個捏造的地址:/cn/album/foobar/277635758?l=en。 在您嘗試訪問之前,您能猜出結果是否可以訪問嗎?

這種設計范式比我現在常用的 URL 設計規范要復雜一些。我的規范要求將資源定位使用兩層 slug 組織,即 $(type)/$(id)。 而蘋果使用了 $(type)/(type-id)/$(id),同時照顧了可讀性和準確性。

題外話 – 為什么 GraphQL 不行

GraphQL[45]?是一種通過使用自定義查詢語言來請求 API 的方式,它的優點在于可以提供更靈活的數據獲取方式。 相比于 RESTful API 需要一次請求獲取所有需要的數據,GraphQL 允許客戶端明確指定需要的數據,從而減少不必要的數據傳輸和處理。

然而,GraphQL 的過于靈活也是它的缺點之一。由于它沒有像 REST API 那樣有一些業務場景建模的規范, 開發人員需要自己考慮數據的處理方式。 這可能導致一些不合理的查詢請求,對后端數據庫造成過度的壓力。此外,GraphQL 的實現和文檔相對較少,也需要更多的學習成本。

因此,雖然 GraphQL 可以在一些特定的場景下提供更好的效果,但它并不適合所有的 API 設計需求。 實際上,一些公司甚至選擇放棄支持 GraphQL,例如 Github 的?一些項目[46]。

最后

Complexity is incremental (復雜度是遞增的) – John Ousterhout (via[47]

風格沒有最好,只有最適合,但是擁有風格是很重要的。

建立一個優秀的規則不僅需要對現有機制有深刻的理解,還需要對業務領域有全面的掌握,并在團隊內進行有效的協作與溝通, 推廣并實施規則。 不過,一旦規則建立起來,就能夠有效降低系統的復雜度,避免隨著時間和業務的推進而不斷增加的復雜性, 并減少研發方面的溝通成本。

這是一項長期的投資,但能夠獲得持久的回報。希望有長遠眼光的人能夠注意到這篇文章。

主要參考文檔:

引用鏈接

[1]?Github v3:?https://docs.github.com/en/rest?apiVersion=2022-11-28
[2]?RESTful API:?https://en.wikipedia.org/wiki/Representational_state_transfer
[3]?阮一峰的 RESTful API 設計指南:?https://www.ruanyifeng.com/blog/2014/05/restful_api.html
[4]?RESTful API 設計最佳實踐:?https://www.oschina.net/translate/best-practices-for-a-pragmatic-restful-api?print
[5]?Leonard Richardson:?https://martinfowler.com/articles/richardsonMaturityModel.html#level0
[6]?JSON:API:?https://jsonapi.org/
[7]?Ruby on Rails:?https://guides.rubyonrails.org/routing.html
[8]?URN(Uniform Resource Name):?https://en.wikipedia.org/wiki/Uniform_Resource_Name
[9]?What’s a slug. and why would I use one? | by Dave Sag:?https://itnext.io/whats-a-slug-f7e74b6c23e0
[10]?Deployment:?https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.26/#deployment-v1-apps
[11]?所有行為(即使是只讀的請求)也應該使用 POST 方法:?https://www.zhihu.com/question/336797348
[12]?知乎網友:?https://www.zhihu.com/people/huixiong-19
[13]?評論:?https://www.zhihu.com/question/336797348/answer/2198634068
[14]?rownum 分頁方案:?https://stackoverflow.com/questions/241622/paging-with-oracle
[15]?MQTT:?https://mqtt.org/
[16]?General Guidelines: API Design | Azure SDKs:?https://azure.github.io/azure-sdk/general_design.html
[17]?Low-Level I/O (The GNU C Library):?https://www.gnu.org/software/libc/manual/html_node/Low_002dLevel-I_002fO.html
[18]?GitHub REST API documentation:?https://docs.github.com/en/rest?apiVersion=2022-11-28
[19]?Endpoints available for GitHub Apps:?https://docs.github.com/en/rest/overview/endpoints-available-for-github-apps?apiVersion=2022-11-28
[20]?API Versions:?https://docs.github.com/en/rest/overview/api-versions?apiVersion=2022-11-28
[21]?Resources in the REST API#client-errors:?https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#client-errors
[22]?Pulls#create-a-pull-request:?https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#create-a-pull-request
[23]?Using pagination in the REST API:?https://docs.github.com/en/rest/guides/using-pagination-in-the-rest-api?apiVersion=2022-11-28
[24]?Commits:?https://docs.github.com/en/rest/commits/commits?apiVersion=2022-11-28
[25]?Resources in the REST API#client-errors:?https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#client-errors
[26]?Resources in the REST API#client-errors:?https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#client-errors
[27]?Resources in the REST API#Timezones:?https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#timezones
[28]?api-guidelines/Guidelines.md at master · microsoft/api-guidelines:?https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md
[29]?Best practices in cloud applications:?https://learn.microsoft.com/en-us/azure/architecture/best-practices/index-best-practices
[30]?Web API design best practices:?https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
[31]?REST API Browser:?https://learn.microsoft.com/en-us/rest/api/?view=Azure
[32]?Azure Storage REST API Reference:?https://learn.microsoft.com/en-us/rest/api/storageservices/
[33]?Lease Container (REST API) – Azure Storage:?https://learn.microsoft.com/en-us/rest/api/storageservices/lease-container?tabs=azure-ad
[34]?Guidelines.md#73-canonical-identifier:?https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#73-canonical-identifier
[35]?Guidelines.md#93-collection-url-patterns:?https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#93-collection-url-patterns
[36]?api-design#define-api-operations-in-terms-of-http-methods:?https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#define-api-operations-in-terms-of-http-methods
[37]?Incremental Copy Blob (REST API) – Azure Storage:?https://learn.microsoft.com/en-us/rest/api/storageservices/incremental-copy-blob
[38]?Job Runs – List – REST API (Azure Storage Mover):?https://learn.microsoft.com/en-us/rest/api/storagemover/job-runs/list?tabs=HTTP
[39]?Agents – Update – REST API (Azure Storage Mover):?https://learn.microsoft.com/en-us/rest/api/storagemover/agents/update?tabs=HTTP
[40]?Guidelines.md#711-http-status-codes:?https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#711-http-status-codes
[41]?Guidelines.md#75-standard-request-headers:?https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md#75-standard-request-headers
[42]?Agents – Create Or Update – REST API (Azure Storage Mover):?https://learn.microsoft.com/en-us/rest/api/storagemover/agents/create-or-update?tabs=HTTP
[43]?Agents – List – REST API (Azure Storage Mover):?https://learn.microsoft.com/en-us/rest/api/storagemover/agents/list?tabs=HTTP
[44]?api-design#use-hateoas-to-enable-navigation-to-related-resources:?https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#use-hateoas-to-enable-navigation-to-related-resources
[45]?GraphQL:?https://graphql.org/
[46]?一些項目:?https://github.blog/changelog/2022-08-18-deprecation-notice-graphql-for-packages/
[47]?via:?https://web.stanford.edu/~ouster/cgi-bin/cs190-winter18/lecture.php?topic=complexity
[48]?api-guidelines/Guidelines.md at master · microsoft/api-guidelines:?https://github.com/Microsoft/api-guidelines/blob/master/Guidelines.md
[49]?GitHub’s APIs:?https://docs.github.com/en/rest/overview/about-githubs-apis?apiVersion=2022-11-28
[50]?Web API design best practices – Azure Architecture Center | Microsoft Learn:?https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design
[51]?API 設計最佳實踐的思考 – 谷樸:?https://developer.aliyun.com/article/701810

本文章轉載微信公眾號@窺豹

上一篇:

四種主流的API風格介紹與對比

下一篇:

One-API實現大語言模型請求接口的統一
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費