本文總結了 RESTful API 設計相關的一些原則,只覆蓋了常見的場景。有些規則只是針對本身項目而言,並不是其餘作法都是錯誤的。html
URI 表示資源,資源通常對應服務器端領域模型中的實體類。git
-
不用下槓_
;URI表示資源的兩種方式:資源集合、單個資源。github
資源集合:數據庫
/zoos //全部動物園 /zoos/1/animals //id爲1的動物園中的全部動物
單個資源:json
/zoos/1 //id爲1的動物園 /zoos/1;2;3 //id爲1,2,3的動物園
/
在url中表達層級,用於按實體關聯關係進行對象導航,通常根據id導航。api
過深的導航容易致使url膨脹,不易維護,如 GET /zoos/1/areas/3/animals/4
,儘可能使用查詢參數代替路徑中的實體導航,如GET /animals?zoo=1&area=3
;瀏覽器
服務器端的組合實體必須在uri中經過父實體的id導航訪問。緩存
組合實體不是first-class的實體,它的生命週期徹底依賴父實體,沒法獨立存在,在實現上一般是對數據庫表中某些列的抽象,不直接對應表,也無id。一個常見的例子是 User — Address,Address是對User表中zipCode/country/city三個字段的簡單抽象,沒法獨立於User存在。必須經過User索引到Address:
GET /user/1/addresses
安全
經過標準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 用的很少,就很少解釋了。
. | 安全性 | 冪等性 |
---|---|---|
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 |
常常使用的、複雜的查詢標籤化,下降維護成本。
如:
GET /trades?status=closed&sort=created,desc
快捷方式:
GET /trades#recently-closed 或者 GET /trades/recently-closed
只用如下常見的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
資源能夠有多種表示方式,如json、xml、pdf、excel等等,客戶端能夠指定本身指望的格式,一般有兩種方式:
http header Accept
:
Accept:application/xml;q=0.6,application/atom+xml;q=1.0
q爲各項格式的偏好程度
/zoo/1.json
不要包裝:
response 的 body 直接就是數據,不要作多餘的包裝。錯誤示例:
{ "success":true, "data":{"id":1,"name":"xiaotuan"}, }
各HTTP方法成功處理後的數據格式:
· | response 格式 |
---|---|
GET | 單個對象、集合 |
POST | 新增成功的對象 |
PUT/PATCH | 更新成功的對象 |
DELETE | 空 |
json格式的約定:
null
字段{ "paging":{"limit":10,"offset":0,"total":729}, "data":[{},{},{}...] }
對第三點的實現稍微多說一點:
Java 服務器端通常用異常表示 RESTful API 的錯誤。API 可能拋出兩類異常:業務異常和非業務異常。業務異常由本身的業務代碼拋出,表示一個用例的前置條件不知足、業務規則衝突等,好比參數校驗不經過、權限校驗失敗。非業務類異常表示不在預期內的問題,一般由類庫、框架拋出,或因爲本身的代碼邏輯錯誤致使,好比數據庫鏈接失敗、空指針異常、除0錯誤等等。
業務類異常必須提供2種信息:
在Controller層使用統一的異常攔截器:
經常使用的http狀態碼及使用場景:
狀態碼 | 使用場景 |
---|---|
400 bad request | 經常使用在參數校驗 |
401 unauthorized | 未經驗證的用戶,常見於未登陸。若是通過驗證後依然沒權限,應該 403(即 authentication 和 authorization 的區別)。 |
403 forbidden | 無權限 |
404 not found | 資源不存在 |
500 internal server error | 非業務類異常 |
503 service unavaliable | 由容器拋出,本身的代碼不要拋這個異常 |
除了資源簡單的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"},{},{}...]
對耗時的異步任務,服務器端接受客戶端傳遞的參數後,應返回建立成功的任務資源,其中包含了任務的執行狀態。客戶端能夠輪訓該任務得到最新的執行進度。
提交任務: 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}
常見的三種方式:
GET /v1/users/1
Accept: application/json+v1
X-Api-Version: 1
用第一種,雖然沒有那麼優雅,但最明顯最方便。
隨着系統發展,總有一些API失效或者遷移,對失效的API,返回404 not found 或 410 gone;對遷移的API,返回 301 重定向。
這個不熟,接觸到的時候再說。