
如何快速實現(xiàn)REST API集成以優(yōu)化業(yè)務流程
pyramid
image by stable difussion, prompt by alswl
這個問題困擾了我很長時間,始于我求學時期,每一次都需要與團隊成員進行交流和討論。 從最初的自由風格到后來的 REST,我經(jīng)常向項目組引用?Github v3[1]?和 Foursqure API(已經(jīng)無法訪問,暴露年齡) 文檔。 然而,在實踐過程中,仍然會有一些與實際工作或公司通用規(guī)范不匹配的情況, 這時候我需要做一些補充工作。最終,我會撰寫一個簡要的?DEVELOPMENT.md
?文檔,以描述設計方案。
但我對該文檔一直有更多的想法,它還不夠完善。因此,我想整理出一份簡單(Simple)而實用(Pragmatic)的 Web API 最佳實踐,也就是本文。
這個問題似乎很明顯,但是深入剖析涉及團隊協(xié)作效率和工程設計哲學。
API(Application Programming Interface,應用程序編程接口)是不同軟件系統(tǒng)之間交互的橋梁。在不同軟件系統(tǒng)之間進行通信時, API 可以通過標準化的方式進行數(shù)據(jù)傳輸和處理,從而實現(xiàn)各種應用程序的集成。
當我們開始撰寫 API 文檔時,就會出現(xiàn)一個范式(Design Pattern),這是顯式還是隱式的, 是每個人一套還是公用同一套。這就像我們使用統(tǒng)一的 USB 接口一樣,統(tǒng)一降低了成本,避免了可能存在的錯誤。具體來說,這有以下幾個原因:
why
image by alswl
雖然使用統(tǒng)一規(guī)范確實有一些成本,需要框架性的了解和推廣,但我相信在大部分場景下, 統(tǒng)一規(guī)范所帶來的收益遠遠高于這些成本。
然而,并非所有的情況下都需要考慮 API 規(guī)范。對于一些短生命周期的項目、影響面非常小的內(nèi)部項目和產(chǎn)品, 可能并不需要過多關注規(guī)范。 此外,在一些特殊的業(yè)務場景下, 協(xié)議底層可能會發(fā)生變化,這時候既有的規(guī)范可能不再適用。但即使如此,我仍然建議重新起草新的規(guī)范,而不是放棄規(guī)范不顧。
在制定 API 規(guī)范時,我們應該遵循一些基本原則,以應對技術上的分歧,我總結了三個獲得廣泛認可的原則:
principle
image by alswl
在 Web API 領域,RESTful API[2]?已經(jīng)成為廣受歡迎的協(xié)議。 其廣泛適用性和受眾范圍之廣源于其與 HTTP 協(xié)議的綁定,這使得 RESTful API 能夠輕松地與現(xiàn)有的 Web 技術進行交互。如果您對 REST 不熟悉, 可以查看?阮一峰的 RESTful API 設計指南[3]?以及?RESTful API 設計最佳實踐[4]。
REST 是一種成熟度較高的協(xié)議,Leonard Richardson[5]?將其描述為四種成熟度級別:
rest-four-level
image by alswl
rel
?鏈接進行 API 資源整合,JSON:API[6]?是登峰造極的表現(xiàn)REST 的核心優(yōu)勢在于:
然而,REST 并非一種具體的協(xié)議或規(guī)范,而是一種風格理念。盡管 REST 定義了一些規(guī)則和原則,如資源的標識、統(tǒng)一接口、無狀態(tài)通信等, 但它并沒有規(guī)定一種具體的實現(xiàn)方式。因此,在實際開發(fā)中,不同的團隊可能會有不同的理解和實踐, 從而導致 API 的不一致性和可維護性降低。
此外,REST 也有一些局限性和缺陷:
/login
)操作,轉換成 session
就非常繞口; 同樣的問題在轉賬這種業(yè)務也會出現(xiàn)。HTTP 有限的動詞無法支撐所有業(yè)務場景。因此,雖然 REST 風格是一個不錯的指導思想,但在具體實現(xiàn)時需要結合具體業(yè)務需求和技術特點,有所取舍,才能實現(xiàn)良好的 API 設計。 最后,我們是否需要 Web API 設計規(guī)范,遵循 REST 風格呢?我認為 REST 能夠解決 90% 的問題,但還有 10% 需要明確規(guī)定細節(jié)。
因為我們的協(xié)議基于 HTTP 和 REST 設計,我們將以 HTTP 請求的四個核心部分為基礎展 開討論,這些部分分別是:URL、Header、Request 和 Response。
我的 URL 設計啟蒙來自于?Ruby on Rails[7]。 在此之前,我總是本能地將模型信息放到 URL 之上,但實際上良好的 URL 設計應該是針對系統(tǒng)信息結構的規(guī)劃。 因此,URL 設計不僅僅要考慮 API,還要考慮面向用戶的 Web URL。
為了達到良好的 URL 設計,我總結了以下幾個規(guī)則:
通常情況下,URL 的模型如下所示:
/$(prefix)/$(module)/$(model)/$(sub-model)/$(verb)?$(query)#${fragment}
其中,Prefix 可能是 API 的版本,也可能是特殊限定,如有些公司會靠此進行接入層分流; Module 是業(yè)務模塊,也可以省略;Model 是模型;SubModel 是子模型,可以省略; Verb 是動詞,也可以省略;Query 是請求參數(shù);Fragment 是 HTTP 原語 Fragment。
需要注意的是,并非所有的組成部分都是必須出現(xiàn)的。例如,SubModel 和 Verb 等字段可 以在不同的 URL 風格中被允許隱藏。
設計風格選擇
注:請注意,方案 A / B / C 之間沒有關聯(lián),每行上下也沒有關聯(lián)
問題 | 解釋(見下方單列分析) | 方案 A | 方案 B | 方案 C |
API Path 里面 Prefix | /apis | /api | 二級域名 | |
Path 里面是否包含 API 版本 | 版本在 URL 的優(yōu)勢 | ? | ?? | |
Path 是否包含 Group | ? | ?? | ||
Path 是否包含動作 | HTTP Verb 不夠用的情況 | ? | ?? (純 REST) | 看情況(如果 HTTP Verb CRUD 無法滿足就包含) |
模型 ID 形式 | Readable Stable Identity 解釋 | 自增 ID | GUID | Readable Stable ID |
URL 中模型單數(shù)還是復數(shù) | 單數(shù) | 復數(shù) | 列表復數(shù),單向單數(shù) | |
資源是一級(平鋪)還是多級(嵌套) | 一級和多級的解釋 | 一級(平鋪) | 多級(嵌套) | |
搜索如何實現(xiàn),獨立接口(/models/search )還是基于列表/models/ 接口 | 獨立 | 合并 | ||
是否有 Alias URL | Alias URL 解釋 | ? | ?? | |
URL 中模型是否允許縮寫(或精簡) | 模型縮寫解釋 | ? | ?? | |
URL 中模型多個詞語拼接的連字符 | - | _ | Camel | |
是否要區(qū)分 Web API 以及 Open API(面向非瀏覽器) | ? | ?? |
我們在設計 URL 時遵循一致性的原則,無論是哪種身份或狀態(tài),都會使用相同的 URL 來訪問同一個資源。 這也是 Uniform Resource Location 的基本原則。雖然我們可以接受不同的內(nèi)容格式(例如 JSON / YAML / HTML / PDF / etc), 但是我們希望資源的位置是唯一的。
然而,問題是,對于同一資源在不同版本之間的呈現(xiàn),是否應該在 URL 中體現(xiàn)呢?這取決于設計者是否認為版本化屬于位置信息的范疇。
根據(jù) RFC 的設計,除了 URL 還有 URN(Uniform Resource Name)[8], 后者是用來標識資源的,而 URL 則指向資源地址。實際上,URN 沒有得到廣泛的使用,以至于 URI 幾乎等同于 URL。
在 REST 設計中,我們需要使用 HTTP 的 GET / POST / PUT / DELETE / PATCH / HEAD 等動詞對資源進行操作。 比如使用 API?GET /apis/books
?查看書籍列別,這個自然且合理。 但是,當需要執(zhí)行類似「借一本書」這樣的動作時, 我們沒有合適的動詞(BORROW)來表示。針對這種情況,有兩種可行的選擇:
POST /apis/books/borrow
,表示借書這一動作;POST /apis/books/borrow-log/
;這個問題在復雜的場景中會經(jīng)常出現(xiàn),例如用戶登錄(POST /api/auth/login
vs POST /api/session
)和帳戶轉賬(vs 轉賬記錄創(chuàng)建)等等。 API 抽象還是具體,始終離不開業(yè)務的解釋。我們不能簡單地將所有業(yè)務都籠統(tǒng)概括到 CRUD 上面, 而是需要合理劃分業(yè)務,以便更清晰地實現(xiàn)和讓用戶理解。
在進行設計時,我們可以考慮是否需要為每個 API 創(chuàng)建一個對應的按鈕來方便用戶的操作。 如果系統(tǒng)中只有一個名為?/api/do
?的 API 并將所有業(yè)務都綁定在其中,雖然技術上可行, 但這種設計不符合業(yè)務需求,每一層的抽象都是為了標準化解決特定問題的解法,TCP L7 設計就是這種理念的體現(xiàn)。
在標記一個資源時,我們通常有幾種選擇:
我個人有一個設計小技巧:使用?${type}/${type-id}
?形式的 slug 來描述標識符。Slug 是一種人類可讀的唯一標識符, 例如?hostname/abc.sqa
?或?ip/172.133.2.1
。 這種設計方式可以在可讀性和唯一性之間實現(xiàn)很好的平衡。
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 的層級設計可以根據(jù)建模來進行,也可以采用直接單層結構的設計。具體問題的解決方式, 例如在設計用戶擁有的書籍時,可以選擇多級結構的 /api/users/foo/books
或一級結構的 /api/books?owner=foo
。
技術上這兩種方案都可以,前者尊重模型的歸屬關系,后者則是注重 URL 結構的簡單。
多級結構更直觀,但也需要解決可能存在的多種組織方式的問題,例如圖書館中書籍按照作者或類別進行組織? 這種情況下,可以考慮在多級結構中明確模型的歸屬關系, 例如 /api/author/foo/books
(基于作者)或 /api/category/computer/books
(基于類別)。
對于一些頻繁使用的 URL,雖然可以按照 URL 規(guī)則進行設計,但我們?nèi)匀豢梢栽O計出一個更為簡潔的 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 中呈現(xiàn)時,由于?/book/book-index
?的 URL 前綴包含了 Book,我們可以減少一層描述, 使 URL 更為簡潔,例如使用?/book/index
。這種技巧在 Web URL 設計中非常常見。
此外,還有一種模型縮寫的策略,即提供一套完整的別名注冊方案。別名是全局唯一的, 例如在 Kubernetes 中,?Deployment[10]?是一種常見的命名,而?apps/v1/Deployment
?是通過添加 Group 限定來表示完整的名稱, 同時還有一個簡寫為?deploy
。這個機制依賴于 Kubernetes 的 API Schema 系統(tǒng)進行注冊和工作。
我們常常會忽略 Header 的重要性。實際上,HTTP 動詞的選擇、HTTP 狀態(tài)碼以及各種身 份驗證邏輯(例如 Cookie / Basic Auth / Berear Token)都依賴于 Header 的設計。
問題 | 解釋(見下方單列分析) | 方案 A | 方案 B | 方案 C |
是否所有 Verb 都使用 POST | 關于全盤 POST | ? | ?? | |
修改(Modify)動作是 POST 還是 PATCH? | POST | PATCH | ||
HTTP Status 返回值 | 2XX 家族 | 充分利用 HTTP Status | 只用核心狀態(tài)(200 404 302 等) | 只用 200 |
是否使用考慮限流系統(tǒng) | ? 429 | ?? | ||
是否使用緩存系統(tǒng) | ? ETag / Last Modify | ?? | ||
是否校驗 UserAgent | ? | ?? | ||
是否校驗 Referrral | ? | ?? |
有些新手(或者自認為有經(jīng)驗的人)可能得出一個錯誤的結論,即除了 GET 請求以外, 所有的 HTTP 請求都應該使用 POST 方法。甚至有些人要求 所有行為(即使是只讀的請求)也應該使用 POST 方法[11]。 這種觀點通常會以“簡單一致”、“避免緩存”或者“運營商的要求”為由來支持。
然而,我們必須明白 HTTP 方法的設計初衷:它是用來描述資源操作類型的,從而派生出了包括緩存、安全、冪等性等一系列問題。 在相對簡單的場景下,省略掉這一層抽象的確不會帶來太大的問題,但一旦進入到復雜的領域中, 使用 HTTP 方法這一層抽象就顯得非常重要了。這是否遵循標準將決定你是否能夠獲得標準化帶來的好處, 類比一下就像一個新的手機廠商可以選擇不使用 USB TypeC 接口。 技術上來說是可行的,但同時也失去了很多標準化支持和大家心智上的約定俗成。
我特別喜歡一位 知乎網(wǎng)友[12] 的 評論[13]:「路由沒有消失,只是轉移了」。
2XX 家族
HTTP 狀態(tài)碼的用途在于表明客戶端與服務器間通信的結果。2XX 狀態(tài)碼系列代表服務器已經(jīng)成功接收、 理解并處理了客戶端請求,回應的內(nèi)容是成功的。以下是 2XX 系列中常見的狀態(tài)碼及其含義:
2XX 系列的狀態(tài)碼表示請求已被成功處理,這些狀態(tài)碼可以讓客戶端明確知曉請求已被正確處理,從而進行下一步操作。
是否需要全面使用 2XX 系列的狀態(tài)碼,取決于是否需要向客戶端明確/顯示的信息, 告知它下一步動作。如果已經(jīng)通過其他方式(包括文檔、口頭協(xié)議)描述清楚, 那么確實可以通盤使用 200 狀態(tài)碼進行返回。但基于行為傳遞含義, 或是基于文檔(甚至口頭協(xié)議)傳遞含義,哪種更優(yōu)秀呢?是更為復雜還是更為簡潔?
設計風格選擇
問題 | 解釋(見下方單列分析) | 方案 A | 方案 B | 方案 C |
復雜的參數(shù)是放到 Form Fields 還是單獨一個 JSON Body | Form Fields | Body | ||
子資源是一次性查詢還是獨立查詢 | 嵌套 | 獨立查詢 | ||
分頁參數(shù)存放 | Header | URL Query | ||
分頁方式 | 分頁方式解釋 | Page based | Offset based | Continuation token |
分頁控制者 | 分頁控制著解釋 | 客戶端 | 服務端 |
分頁方式解釋
我們最為常見的兩種分頁方式是 Page-based 和 Offset-based,可以通過公式進行映射。 此外,還存在一種稱為 Continuation Token 的方式,其技術類似于 Oracle 的 rownum 分頁方案[14],使用參數(shù) start-from=?
進行描述。 雖然 Continuation Token 的優(yōu)缺點都十分突出,使用此種方式可以將順序性用于替代隨機性。
分頁控制著解釋
在某些情況下,我們需要區(qū)分客戶端分頁(Client Pagination)和服務器分頁(Server Pagniation)。 客戶端分頁是指下一頁的參數(shù)由客戶端計算而來,而服務器分頁則是由服務器返回?rel
?或 JSON.API 等協(xié)議。 使用服務器分頁可以避免一些問題,例如批量屏蔽了一些內(nèi)容,如果使用客戶端分頁,可能會導致缺頁或者白屏。
設計風格選擇
問題 | 解釋(見下方單列分析) | 方案 A | 方案 B | 方案 C |
模型呈現(xiàn)種類 | 模型的幾種形式 | 單一模型 | 多種模型 | |
大模型如何包含子模型模型 | 模型的連接、側載和嵌入 | 嵌入 | 核心模型 + 多次關聯(lián)資源查詢 | 鏈接 |
字段返回是按需還是歸并還是統(tǒng)一 | 統(tǒng)一 | 使用 fields 字段按需 | ||
字段表現(xiàn)格式 | Snake | Camel | ||
錯誤碼 | 無自定,使用 Message | 自定義 | ||
錯誤格式 | 全局統(tǒng)一 | 按需 | ||
時區(qū) | UTC | Local | Local + TZ | |
HATEOAS | ? | ?? |
模型的幾種形式
在 API 設計中,對于模型的表現(xiàn)形式有多種定義。雖然這并不是 API 規(guī)范必須討論的話題,但它對于 API 設計來說是非常重要的。
我將模型常說的模型呈現(xiàn)方式分為一下幾類,這并非是專業(yè)的界定,借用了 Java 語境下面的一些定義。 這些名稱在不同公司甚至不同團隊會有不一樣的叫法:
models
image by alswl
除此之外,還經(jīng)常使用兩類:Rich Model 和 Tiny Model(請忽略命名,不同團隊叫法差異比較大):
模型的連接、側載和嵌入
在 API 設計中,我們經(jīng)常需要處理一個模型中包含多個子模型的情況,例如 Book 包含 Comments。 對于這種情況,通常有三種表現(xiàn)形式可供選擇:鏈接(Link)、側載(Side)和嵌入(Embed)。
models-with-children
image by alswl
鏈接(有時候這個 URL 也會隱藏,基于客戶端和服務端的隱式協(xié)議進行請求):
{
"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": "魯迅原名周樹人"
}
]
}
}
還有一些問題沒有收斂在四要素里面,但是我們在工程實踐中也經(jīng)常遇到,我將其捋出來:
我不是 HTTP 協(xié)議,怎么辦?
Web API 中較少遇到非 HTTP 協(xié)議,新建一套協(xié)議的成本太高了。在某些特定領域會引入一些協(xié)議, 比如 IoT 領域的?MQTT[15]。
此外,RPC 是一個涉及廣泛領域的概念,其內(nèi)容遠遠不止于協(xié)議層面。 通常我們會將 HTTP 和 RPC 的傳輸協(xié)議以及序列化協(xié)議進行對比。 我認為,本文中的許多討論也對 RPC 領域具有重要意義。
有些團隊或個人計劃使用自己創(chuàng)建的協(xié)議,但我的觀點是應盡量避免自建協(xié)議,因為真正需要創(chuàng)建協(xié)議的情況非常罕見。 如果確實存在強烈的需要,那么我會問兩個問題:是否通讀過 HTTP RFC 文檔和 HTTP/2 RFC 文檔?
我不是遠程服務(RPC / HTTP 等),而是 SDK 怎么辦?
本文主要討論的是 Web API(HTTP)的設計規(guī)范,并且其中一些規(guī)則可以借鑒到 RPC 系統(tǒng)中。 然而,討論的基礎都是建立在遠程服務(Remote Service)的基礎之上的。 如果你是 SDK 開發(fā)人員,你會有兩個角色,可能會作為客戶端和遠程服務器進行通信, 同時還會作為 SDK 提供面向開發(fā)人員的接口。對于后者,以下幾個規(guī)范可以作為參考:
后者可以參考一下這么幾個規(guī)范:
認證鑒權方案
一般而言,Web API 設計中會明確描述所采用的認證和鑒權系統(tǒng)。 需要注意區(qū)分「認證」和「鑒權」兩個概念。關于「認證」這一話題,可以在單獨的章節(jié)中進行討論,因此本文不會展開這一方面的內(nèi)容。
在 Web API 設計中,常見的認證方式包括:HTTP Basic Auth、OAuth2 和賬號密碼登錄等。 常用的狀態(tài)管理方式則有 Bearer Token 和 Cookie。此外,在防篡改等方面,還會采用基于 HMac 算法的防重放和篡改方案。
忽略掉的話題
在本次討論中,我未涉及以下話題:異步協(xié)議(Web Socket / Long Pulling / 輪訓)、CORS、以及安全問題。 雖然這些話題重要,但是在本文中不予展開。
什么時候打破規(guī)則
有些開發(fā)者認為規(guī)則就是為了打破而存在的?,F(xiàn)實往往非常復雜,我們難以討論清楚各個細節(jié)。 如果開發(fā)者覺得規(guī)則不符合實際需求,有兩種處理方式:修改規(guī)則或打破規(guī)則。 然而,我更傾向于討論和更新規(guī)則,明確規(guī)范不足之處,確定是否存在特殊情況。 如果確實需要創(chuàng)建特例,一定要在文檔中詳細描述,告知接任者和消費者這是一個特例,說明特例產(chǎn)生的原因以及特例是如何應對的。
Github 的 API 是我常常參考的對象。它對其業(yè)務領域建模非常清晰,提供了詳盡的文檔,使得溝通成本大大降低。 我主要參考以下兩個鏈接: 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 中模型單數(shù)還是復數(shù) | 復數(shù) | |
資源是一級(平鋪)還是多級(嵌套) | 多級 | |
搜索如何實現(xiàn),獨立接口(/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 |
是否要區(qū)分 Web API 以及 Open API(面向非瀏覽器) | ?? | |
Header | ||
是否所有 Verb 都使用 POST | ?? | |
修改(Modify)動作是 POST 還是 PATCH? | PATCH | |
HTTP Status 返回值 | 充分利用 HTTP Status | 常用,包括限流洗損 |
是否使用考慮限流系統(tǒng) | ? 429 | |
是否使用緩存系統(tǒng) | ? ETag / Last Modify | Resources in the REST API#client-errors[21] |
是否校驗 UserAgent | ? | |
是否校驗 Referrral | ?? | |
Request | ||
復雜的參數(shù)是放到 Form Fields 還是單獨一個 JSON Body | Body | 參考 Pulls#create-a-pull-request[22] |
子資源是一次性查詢還是獨立查詢 | 嵌套 | 從 Pulls 進行判斷 |
分頁參數(shù)存放 | URL Query | |
分頁方式 | Page | Using pagination in the REST API[23] |
分頁控制者 | 服務端 | 同上 |
Response | ||
模型呈現(xiàn)種類 | 多種模型 | 比如 Commits 里面的 明細和 Parent Commits[24] |
大模型如何包含子模型模型 | 核心模型 + 多次關聯(lián)資源查詢? | 沒有明確說明,根據(jù)幾個核心 API 反推 |
字段返回是按需還是歸并還是統(tǒng)一 | 統(tǒng)一 | |
字段表現(xiàn)格式 | Snake | |
錯誤碼 | 無 | Resources in the REST API#client-errors[25] |
錯誤格式 | 全局統(tǒng)一 | Resources in the REST API#client-errors[26] |
時區(qū) | 復合方案(ISO 8601 > Time-Zone Header > User Last > UTC) | Resources in the REST API#Timezones[27] |
HATEOAS | ?? |
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 的產(chǎn)品線遠比 Github 豐富,一些 API 也沒有遵循 Azure 自己的規(guī)范。 在找實例時候,我主要參考?REST API Browser[31],?Azure Storage REST API Reference[32]。 如果具體實現(xiàn)和 Guidelines.md 沖突,我會采用 Guidelines.md 結論。
問題 | 選擇 | 備注 |
URL | ||
API Path 里面 Prefix | 二級域名 | |
Path 里面是否包含 API 版本 | ?? | x-ms-version |
Path 是否包含 Group | ? | |
Path 是否包含動作 | ??? | 沒有明確說明,但是有傾向使用 comp 參數(shù)來進行動作,保持 URL 的 RESTful 參考 Lease Container (REST API) – Azure Storage[33] |
模型 ID 形式 | Readable Stable Identity | Guidelines.md#73-canonical-identifier[34] |
URL 中模型單數(shù)還是復數(shù) | 復數(shù) | Guidelines.md#93-collection-url-patterns[35] |
資源是一級(平鋪)還是多級(嵌套) | 多級 / 一級 | api-design#define-api-operations-in-terms-of-http-methods[36],注 MS 有 comp=? 這種參數(shù),用來處理特別的命令 |
搜索如何實現(xiàn),獨立接口(/models/search )還是基于列表/models/ 接口 | ? | 傾向于基于列表,因為大量使用 comp= 這個 URL Param 來進行子命令,比如 Incremental Copy Blob (REST API) – Azure Storage[37] |
是否有 Alias URL | ? | |
URL 中模型是否允許縮寫(或精簡) | ? | |
URL 中模型多個詞語拼接的連字符 | Camel | Job Runs – List – REST API (Azure Storage Mover)[38] |
是否要區(qū)分 Web API 以及 Open API(面向非瀏覽器) | ?? | |
Header | ||
是否所有 Verb 都使用 POST | ?? | |
修改(Modify)動作是 POST 還是 PATCH? | PATCH | Agents – Update – REST API (Azure Storage Mover)[39] |
HTTP Status 返回值 | 充分利用 HTTP Status | Guidelines.md#711-http-status-codes[40] |
是否使用考慮限流系統(tǒng) | ? | |
是否使用緩存系統(tǒng) | ? | Guidelines.md#75-standard-request-headers[41] |
是否校驗 UserAgent | ?? | |
是否校驗 Referrral | ?? | |
Request | ||
復雜的參數(shù)是放到 Form Fields 還是單獨一個 JSON Body | Body | 參考 Agents – Create Or Update – REST API (Azure Storage Mover)[42] |
子資源是一次性查詢還是獨立查詢 | ? | |
分頁參數(shù)存放 | ? | 沒有結論 |
分頁方式 | Page based | |
分頁控制者 | 服務端 | Agents – List – REST API (Azure Storage Mover)[43] |
Response | ||
模型呈現(xiàn)種類 | 單一模型 | 推測 |
大模型如何包含子模型模型 | ? | 場景過于復雜,沒有單一結論 |
字段返回是按需還是歸并還是統(tǒng)一 | ? | |
字段表現(xiàn)格式 | Camel | |
錯誤碼 | 使用自定錯誤碼清單 | 至少在各自產(chǎn)品內(nèi) |
錯誤格式 | 自定義 | |
時區(qū) | ? | |
HATEOAS | ? | api-design#use-hateoas-to-enable-navigation-to-related-resources[44] |
Azure 的整體設計風格要比 Github API 更復雜,同一個產(chǎn)品的也有多個版本的差異,看 上去統(tǒng)一性要更差一些。這種復雜場景想用單一的規(guī)范約束所有團隊的確也是更困難的。 我們可以看到 Azaure 團隊在 Guidelines 上面努力,他們最近正在推出 vNext 規(guī)范。
我個人風格基本繼承自 Github API 風格,做了一些微調(diào),更適合中小型產(chǎn)品開發(fā)。 我的改動原因都在備注中解釋,改動出發(fā)點是:簡化 / 減少歧義 / 考慮實際成本。如果備注里面標記了「注」,則是遵循 Github 方案并添加一些觀點。
問題 | 選擇 | 備注 |
URL | ||
API Path 里面 Prefix | /apis | 我們往往只有一個系統(tǒng),一個域名要承載 API 和 Web Page |
Path 里面是否包含 API 版本 | ? | |
Path 是否包含 Group | ? | 做一層業(yè)務模塊拆分,隔離一定合作邊界 |
Path 是否包含動作 | 看情況(如果 HTTP Verb CRUD 無法滿足就包含) | |
模型 ID 形式 | Readable Stable Identity | |
URL 中模型單數(shù)還是復數(shù) | 復數(shù) | |
資源是一級(平鋪)還是多級(嵌套) | 多級 + 一級 | 注:80% 情況都是遵循模型的歸屬,少量情況(常見在搜索)使用一級 |
搜索如何實現(xiàn),獨立接口(/models/search )還是基于列表/models/ 接口 | 統(tǒng)一 > 獨立 | 低成本實現(xiàn)一些(早期 Github Issue 也是沒有 /search 接口 |
是否有 Alias URL | ?? | 簡單點 |
URL 中模型是否允許縮寫(或精簡) | ? | 一旦做了精簡,需要在術語表標記出來 |
URL 中模型多個詞語拼接的連字符 | - | |
是否要區(qū)分 Web API 以及 Open API(面向非瀏覽器) | ?? | |
Header | ||
是否所有 Verb 都使用 POST | ?? | |
修改(Modify)動作是 POST 還是 PATCH? | PATCH | |
HTTP Status 返回值 | 充分利用 HTTP Status | |
是否使用考慮限流系統(tǒng) | ? 429 | |
是否使用緩存系統(tǒng) | ?? | 簡單一些,使用動態(tài)數(shù)據(jù),去除緩存能力 |
是否校驗 UserAgent | ? | |
是否校驗 Referrral | ?? | |
Request | ||
復雜的參數(shù)是放到 Form Fields 還是單獨一個 JSON Body | Body | |
子資源是一次性查詢還是獨立查詢 | 嵌套 | |
分頁參數(shù)存放 | URL Query | |
分頁方式 | Page | |
分頁控制者 | 客戶端 | 降低服務端成本,容忍極端情況空白 |
Response | ||
模型呈現(xiàn)種類 | 多種模型 | 使用的 BO / VO / Tiny / Rich |
大模型如何包含子模型模型 | 核心模型 + 多次關聯(lián)資源查詢 | |
字段返回是按需還是歸并還是統(tǒng)一 | 統(tǒng)一 | Tiny Model(可選) / Model(默認) / Rich Model(可選) |
字段表現(xiàn)格式 | Snake | |
錯誤碼 | 無 | 注:很多場景只要 message |
錯誤格式 | 全局統(tǒng)一 | |
時區(qū) | ISO 8601 | 只使用一種格式,不再支持多種方案 |
HATEOAS | ?? |
Apple Music
image from Apple Music
我最近在使用 Apple Music 時注意到了其 Web 頁面的 URL 結構:
/cn/album/we-sing-we-dance-we-steal-things/277635758?l=en
仔細看這個 URL 結構,可以發(fā)現(xiàn)其中 Path 包含了人類可讀的 slug,分為三個部分:alumn/$(name)/$(id)
(其中包含了 ID)。 我立即想到了一個問題:中間的可讀名稱是否無機器意義,純粹面向自然人? 于是我測試了一個捏造的地址:/cn/album/foobar/277635758?l=en
。 在您嘗試訪問之前,您能猜出結果是否可以訪問嗎?
這種設計范式比我現(xiàn)在常用的 URL 設計規(guī)范要復雜一些。我的規(guī)范要求將資源定位使用兩層 slug 組織,即 $(type)/$(id)
。 而蘋果使用了 $(type)/(type-id)/$(id)
,同時照顧了可讀性和準確性。
GraphQL[45]?是一種通過使用自定義查詢語言來請求 API 的方式,它的優(yōu)點在于可以提供更靈活的數(shù)據(jù)獲取方式。 相比于 RESTful API 需要一次請求獲取所有需要的數(shù)據(jù),GraphQL 允許客戶端明確指定需要的數(shù)據(jù),從而減少不必要的數(shù)據(jù)傳輸和處理。
然而,GraphQL 的過于靈活也是它的缺點之一。由于它沒有像 REST API 那樣有一些業(yè)務場景建模的規(guī)范, 開發(fā)人員需要自己考慮數(shù)據(jù)的處理方式。 這可能導致一些不合理的查詢請求,對后端數(shù)據(jù)庫造成過度的壓力。此外,GraphQL 的實現(xiàn)和文檔相對較少,也需要更多的學習成本。
因此,雖然 GraphQL 可以在一些特定的場景下提供更好的效果,但它并不適合所有的 API 設計需求。 實際上,一些公司甚至選擇放棄支持 GraphQL,例如 Github 的?一些項目[46]。
Complexity is incremental (復雜度是遞增的) – John Ousterhout (via[47])
風格沒有最好,只有最適合,但是擁有風格是很重要的。
建立一個優(yōu)秀的規(guī)則不僅需要對現(xiàn)有機制有深刻的理解,還需要對業(yè)務領域有全面的掌握,并在團隊內(nèi)進行有效的協(xié)作與溝通, 推廣并實施規(guī)則。 不過,一旦規(guī)則建立起來,就能夠有效降低系統(tǒng)的復雜度,避免隨著時間和業(yè)務的推進而不斷增加的復雜性, 并減少研發(fā)方面的溝通成本。
這是一項長期的投資,但能夠獲得持久的回報。希望有長遠眼光的人能夠注意到這篇文章。
主要參考文檔:
[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]
?知乎網(wǎng)友:?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
本文章轉載微信公眾號@窺豹