Restful API 的設計規範
本文總結了 RESTful API 設計相關的一些原則,只覆蓋了常見的場景。有些規則只是針對自己專案而言,並非其他做法都是錯誤的。
1. URI
URI 表示資源,資源一般對應伺服器端領域模型中的實體類。
URI規範
- 不用大寫;
- 用中槓
-
不用下槓_
; - 引數列表要encode;
- URI中的名詞表示資源集合,使用複數形式。
資源集合 vs 單個資源
URI表示資源的兩種方式:資源集合、單個資源。
資源集合:
/zoos //所有動物園
/zoos/1/animals //id為1的動物園中的所有動物
單個資源:
/zoos/1 //id為1的動物園
/zoos/1;2;3 //id為1,2,3的動物園
避免層級過深的URI
/
在url中表達層級,用於按實體關聯關係進行物件導航,一般根據id導航。
過深的導航容易導致url膨脹,不易維護,如 GET /zoos/1/areas/3/animals/4
,儘量使用查詢引數代替路徑中的實體導航,如GET /animals?zoo=1&area=3
;
對Composite資源的訪問
伺服器端的組合實體必須在uri中通過父實體的id導航訪問。
組合實體不是first-class的實體,它的生命週期完全依賴父實體,無法獨立存在,在實現上通常是對資料庫表中某些列的抽象,不直接對應表,也無id。一個常見的例子是 User — Address,Address是對User表中zipCode/country/city三個欄位的簡單抽象,無法獨立於User存在。必須通過User索引到Address:
GET /user/1/addresses
2. Request
HTTP方法
通過標準HTTP方法對資源CRUD:
GET:查詢
GET /zoos
GET /zoos/1
GET /zoos/1/employees
POST:建立單個資源。POST一般向“資源集合”型uri發起
POST /animals //新增動物
POST /zoos/1/employees //為id為1的動物園僱傭員工
PUT:更新單個資源(全量),客戶端提供完整的更新後的資源。與之對應的是 PATCH,PATCH 負責部分更新,客戶端提供要更新的那些欄位。PUT/PATCH一般向“單個資源”型uri發起
PUT /animals/1
PUT /zoos/1
DELETE:刪除
DELETE /zoos/1/employees/2
DELETE /zoos/1/employees/2;4;5
DELETE /zoos/1/animals //刪除id為1的動物園內的所有動物
HEAD / OPTION 用的不多,就不多解釋了。
安全性和冪等性
- 安全性:不會改變資源狀態,可以理解為只讀的;
- 冪等性:執行1次和執行N次,對資源狀態改變的效果是等價的。
. | 安全性 | 冪等性 |
---|---|---|
GET | √ | √ |
POST | × | × |
PUT | × | √ |
DELETE | × | √ |
安全性和冪等性均不保證反覆請求能拿到相同的response。以 DELETE 為例,第一次DELETE返回200表示刪除成功,第二次返回404提示資源不存在,這是允許的。
複雜查詢
查詢可以捎帶以下引數:
. | 示例 | 備註 |
---|---|---|
過濾條件 | ?type=1&age=16 | 允許一定的uri冗餘,如/zoos/1 與/zoos?id=1 |
排序 | ?sort=age,desc | |
投影 | ?whitelist=id,name,email | |
分頁 | ?limit=10&offset=3 |
Bookmarker
經常使用的、複雜的查詢標籤化,降低維護成本。
如:
GET /trades?status=closed&sort=created,desc
快捷方式:
GET /trades#recently-closed
或者
GET /trades/recently-closed
Format
只用以下常見的3種body format:
- Content-Type: application/json
POST /v1/animal HTTP/1.1 Host: api.example.org Accept: application/json Content-Type: application/json Content-Length: 24 { "name": "Gir", "animalType": "12" }
- Content-Type: application/x-www-form-urlencoded (瀏覽器POST表單用的格式)
POST /login HTTP/1.1 Host: example.com Content-Length: 31 Accept: text/html Content-Type: application/x-www-form-urlencoded username=root&password=Zion0101
- Content-Type: multipart/form-data; boundary=—-RANDOM_jDMUxq4Ot5 (表單有檔案上傳時的格式)
Content Negotiation
資源可以有多種表示方式,如json、xml、pdf、excel等等,客戶端可以指定自己期望的格式,通常有兩種方式:
- http header
Accept
:Accept:application/xml;q=0.6,application/atom+xml;q=1.0
q為各項格式的偏好程度
- url中加檔案字尾:
/zoo/1.json
6. Response
- 不要包裝:
response 的 body 直接就是資料,不要做多餘的包裝。錯誤示例:{ "success":true, "data":{"id":1,"name":"xiaotuan"}, }
- 各HTTP方法成功處理後的資料格式:
· response 格式 GET 單個物件、集合 POST 新增成功的物件 PUT/PATCH 更新成功的物件 DELETE 空 - json格式的約定:
- 時間用長整形(毫秒數),客戶端自己按需解析(moment.js)
- 不傳
null
欄位
分頁response
{
"paging":{"limit":10,"offset":0,"total":729},
"data":[{},{},{}...]
}
7. 錯誤處理
- 不要發生了錯誤但給2xx響應,客戶端可能會快取成功的http請求;
- 正確設定http狀態碼,不要自定義;
- Response body 提供 1) 錯誤的程式碼(日誌/問題追查);2) 錯誤的描述文字(展示給使用者)。
對第三點的實現稍微多說一點:
Java 伺服器端一般用異常表示 RESTful API 的錯誤。API 可能丟擲兩類異常:業務異常和非業務異常。業務異常由自己的業務程式碼丟擲,表示一個用例的前置條件不滿足、業務規則衝突等,比如引數校驗不通過、許可權校驗失敗。非業務類異常表示不在預期內的問題,通常由類庫、框架丟擲,或由於自己的程式碼邏輯錯誤導致,比如資料庫連線失敗、空指標異常、除0錯誤等等。
業務類異常必須提供2種資訊:
- 如果丟擲該類異常,HTTP 響應狀態碼應該設成什麼;
- 異常的文字描述;
在Controller層使用統一的異常攔截器:
- 設定 HTTP 響應狀態碼:對業務類異常,用它指定的 HTTP code;對非業務類異常,統一500;
- Response Body 的錯誤碼:異常類名
- Response Body 的錯誤描述:對業務類異常,用它指定的錯誤文字;對非業務類異常,線上可以統一文案如“伺服器端錯誤,請稍後再試”,開發或測試環境中用異常的 stacktrace,伺服器端提供該行為的開關。
常用的http狀態碼及使用場景:
狀態碼 | 使用場景 |
---|---|
400 bad request | 常用在引數校驗 |
401 unauthorized | 未經驗證的使用者,常見於未登入。如果經過驗證後依然沒許可權,應該 403(即 authentication 和 authorization 的區別)。 |
403 forbidden | 無許可權 |
404 not found | 資源不存在 |
500 internal server error | 非業務類異常 |
503 service unavaliable | 由容器丟擲,自己的程式碼不要拋這個異常 |
8. 服務型資源
除了資源簡單的CRUD,伺服器端經常還會提供其他服務,這些服務無法直接用上面提到的URI對映。如:
- 按關鍵字搜尋;
- 計算地球上兩點間的距離;
- 批量向使用者推送訊息
可以把這些服務看成資源,計算的結果是資源的presentation,按服務屬性選擇合適的HTTP方法。
例:
GET /search?q=filter?category=file 搜尋
GET /distance-calc?lats=47.480&lngs=-122.389&late=37.108&lnge=-122.448
POST /batch-publish-msg
[{"from":0,"to":1,"text":"abc"},{},{}...]
9. 非同步任務
對耗時的非同步任務,伺服器端接受客戶端傳遞的引數後,應返回建立成功的任務資源,其中包含了任務的執行狀態。客戶端可以輪訓該任務獲得最新的執行進度。
提交任務:
POST /batch-publish-msg
[{"from":0,"to":1,"text":"abc"},{},{}...]
返回:
{"taskId":3,"createBy":"Anonymous","status":"running"}
GET /task/3
{"taskId":3,"createBy":"Anonymous","status":"success"}
如果任務的執行狀態包括較多資訊,可以把“執行狀態”抽象成組合資源,客戶端查詢該狀態資源瞭解任務的執行情況。
提交任務:
POST /batch-publish-msg
[{"from":0,"to":1,"text":"abc"},{},{}...]
返回:
{"taskId":3,"createBy":"Anonymous"}
GET /task/3/status
{"progress":"50%","total":18,"success":8,"fail":1}
10. API的演進
版本
常見的三種方式:
- 在uri中放版本資訊:
GET /v1/users/1
- Accept Header:
Accept: application/json+v1
- 自定義 Header:
X-Api-Version: 1
用第一種,雖然沒有那麼優雅,但最明顯最方便。
URI失效
隨著系統發展,總有一些API失效或者遷移,對失效的API,返回404 not found 或 410 gone;對遷移的API,返回 301 重定向。
11. 安全
這個不熟,接觸到的時候再說。
參考文件
- < RESTful Web Services Cookbook >
- Consumer-Centric API Design
- RESTful Best Practices
相關文章
- RESTful API 設計規範RESTAPI
- RESTful 介面設計規範 筆記REST筆記
- restful介面設計規範總結REST
- API 介面設計規範API
- RESETful API 設計規範API
- restFul介面設計規範[僅供參考]REST
- RESTful 介面設計規範與mock的完美結合RESTMock
- 基於工程經驗的『RESTful介面設計規範』REST
- RESTful API 中的 Status code 是否要遵守規範RESTAPI
- 理解RESTful Api設計RESTAPI
- restful api設計指南RESTAPI
- RESTFUL API 安全設計指南RESTAPI
- RestFul Api 設計 之 URLRESTAPI
- 好RESTful API的設計原則RESTAPI
- GitHub 的 Restful HTTP API 設計分解GithubRESTHTTPAPI
- drf : web應用模式,RESTful API規範,介面測試工具:PostmanWeb模式RESTAPIPostman
- Restful規範-APIView原始碼分析RESTAPIView原始碼
- MySQL資料庫規範 (設計規範+開發規範+操作規範)MySql資料庫
- 名片設計規範
- Greenplum索引設計的規範索引
- Django RESTful API設計與實踐指南DjangoRESTAPI
- 討論下 RESTful 風格 API 的路由設計RESTAPI路由
- RESTful & “優雅的”API 響應結構設計RESTAPI
- MySQL 規範 (資料庫表設計規範)MySql資料庫
- 移動端UI設計規範模板參考以及設計規範的好處UI
- 網頁設計的基本規範網頁
- Rest Framework設計規範RESTFramework
- python程式設計規範Python程式設計
- JS程式設計規範JS程式設計
- PCB Stack設計規範
- React程式設計規範React程式設計
- 構建高效的 API 規範API
- 【譯】Android API 規範AndroidAPI
- 程式設計小記-程式設計規範程式設計
- 常見的資料設計規範
- MongoDB資料庫的設計規範MongoDB資料庫
- ios12設計規範iOS
- Java開發中RestFul服務介面規範JavaREST