背景
很多團隊都在構建API,并且聲稱自己團隊創(chuàng)建的API都是足夠的RESTful,今天我們簡單聊下RESTful API相關的一些概念和設計實踐。
定義
REST(Representational State Transfer) - 表述性狀態(tài)轉移
簡單一句是就指: 服務器發(fā)送表述用于描述資源的當前狀態(tài),客戶端發(fā)送表述用于描述客服端希望資源擁有的狀態(tài)。
REST 是一種架構風格。定義了分布式系統(tǒng)中,各個組件之間的交互方式。
Richardson成熟度模型

- LEVEL 0, 只用HTTP作為傳輸通道,不會使用HTTP的任何額外機制。例如SOAP、 RPC
- LEVEL 1, 引入資源的概念,使用不同的URI完成不同的功能。
GET /books?author=“john”
Response:
[
{“id”:11, “Game of thrones”},
{“id”:22, “Notre Dame de Paris”}
]
GET /orders?action=create&bookId=11&quantity=2
Response:
{
“id”: 3,
“book-id”: 11,
“quantity”: 2
}
GET /orders/3/delete
- LEVEL 2, 用不同的URI定位資源,用不同的HTTP方法操作資源-CRUD。
GET /books?author=“john”
200 OK
[
{“id”:11, “Game of thrones”},
{“id”:22, “Notre Dame de Paris”}
]
POST /orders
{
“bookId”: 11
“quantity”: 2,
}
201 CREATED
Location: /orders/3
{
“id”: “3”
“bookId”: 11
“quantity”: 2
}
DELETE /orders/3
-
LEVEL 3 - HATEOAS, 在返回的Representation中包含了與該資源相關資源的鏈接, 降低客戶端編程錯誤(
大約90%的錯誤出現(xiàn)在為服務器構造正確URI的過程中), 減少無效的狀態(tài)轉換調(diào)用。
GET /books?author=“john”
200 OK
{
“data”: {
[
{“id”:11, “Game of thrones”},
{“id”:22, “Notre Dame de Paris”}
]
},
“l(fā)inks”: {
“self”: “/books?author=john”
“order”: “/orders”
}
}
POST /orders
{
“bookId”: 11
“quantity”: 2,
}
201 CREATED
Location: /orders/3
{
“data”:{
“id”: “3”
“bookId”: 11
“quantity”: 2
},
“l(fā)inks”: {
“self”: “/orders/3”,
“payment”: “/payments?discount=95”
}
}
DELETE /orders/3
對照以上的成熟度模型,我們可以比較明確的知道我們自己的API,屬于哪個LEVEL?
API規(guī)范
無狀態(tài)原則
服務端必須是沒有狀態(tài)的,換句話說,客戶端的所有請求必須包括服務端完成請求的所有信息(e.g: 認證信息,表單數(shù)據(jù));客戶端不能假設服務端有任何的狀態(tài)信息,所有的狀態(tài)信息只有兩種方式保持:
- 資源狀態(tài)
- 客戶端保存
服務無狀態(tài),很好的方便了水平擴展,高可用。
冪等原則
一次和多次請求某一個資源應該具有相同的副作用
冪等的方法意味著請求成功執(zhí)行所得到的結果不依賴于該方法被執(zhí)行的次數(shù);這在分布式事務-特別是最終一致性的時候,我們都需要保證業(yè)務服務的冪等性息息相關。
在常見的HTTP Verbs里面,GET是天然的滿足冪等性,為緩存提供了條件;DELETE和PUT/PATCH都可以實現(xiàn)冪等。
可緩存原則
在請求和響應的過程中,任何一個節(jié)點都可能會”緩存“響應數(shù)據(jù), 因此響應都會顯式或者隱式的包含”能否被緩存“的信息, 客服端和中間涉及的節(jié)點會根據(jù)這些信息,來進行后續(xù)處理。
比如: 服務端可以使用Cache-Control等Http Header字段來控制緩存的期限,良好的緩存策略可以減少客戶端-服務端的交互,降低服務端的負載,從而提供性能和可擴展性。
HTTP/1.1 200 OK
Date: Fri, 26 Mar 2018 09:33:49 GMT
Cache-Control: max-age=3600
Last-Modified: Fri, 26 Mar 2018 09:33:49 GMT
ETag: cde893c4
安全原則
在做API設計的的時候,以下是在實際開發(fā)中常遇到的安全問題:
- 缺失了對資源從屬關系的檢查,對于URL中只出現(xiàn)一個資源的情況,絕大多數(shù)開發(fā)者都會知道做安全防御,然而,問題往往出現(xiàn)在包括多個資源的時候,e.g: /users/1/orders/239843,應用只檢查了當前請求發(fā)起者是否是編號 為1的用戶,以及編號為239843的訂單是否存在,有很大的概率沒有檢查URL中的訂單和用戶之間的從屬關系
-
HTTP響應中缺失必要的Security Header, 請合理使用以下的Header,可使得你開發(fā)的API具備更高的安全性。
- X-XSS-Protection
- Strict-Transport-Security
- X-Content-Type-Options
- X-Frame-Options
- 泄露業(yè)務信息,在返回體中,不要暴露多余的信息,特別是銘感信息。
- API缺乏速率限制的保護
兼容性原則
- 當API出現(xiàn)較大升級并且包含破壞性改動的時候,服務提供者需要提供新的API版本,與此同時,需要保持對老版本API的支持,直到達到棄用的標準。
- 常將API版本放在URL中,比如/v2/users/{uid};
- 或者放在http header中,
Accept: application/vnd.api+json;version=2
- 在更改已有API的時候,不能更改字段的含義,只添加可選字段,不添加必選字段,當資源URL發(fā)生變化的時候,支持重定向。
API設計
API的設計遵循上面的四個原則,同時需要根據(jù)業(yè)務定義資源,用URI定位資源,并用HTTP verb來操作資源。
定義Resource
一切可以被命名的信息都可以叫做資源(Resource),資源是名詞,不要是動詞,e.g: 一個用戶關注了另一個用戶,這個資源應該被定義成“關系(relationship)”,而不是“關注(follow)”。
用URI定位資源
GET /users/{id}
POST /users
PUT/PATCH /users/{id}
DELETE /users/{id}
在我們團隊中,定義了設計URL的軍規(guī):
- 使用復數(shù),不管它代表的是一個資源還是一個資源集合 GET /users/123/orders/345
- 使用“-”,分離多個單詞,而不是駝峰, e.g: GET /users/{id}/account-type
JSON-API
最后,開發(fā)API的過程中,會花很多時間去和API的消費者溝通API接口的具體格式。例如Content-Type,JSON的字段的定義等等,需要耗費大量的精力,如果遵循共同的約定,可以提高開發(fā)效率,利用更普遍的工具,使開發(fā)者更加專注于開發(fā)重點,這里向大家推薦:JSON API Specification: http://jsonapi.org
- meta:輔助信息
- data:主體信息
- attributes:資源的數(shù)據(jù)
- errors:錯誤
- links:鏈接
GET /users/123
{
”meta”: {…},
“data”: {
“type”: “user”,
“id”: “123”,
“attributes”: {},
“relationships”: {},
},
“errors”: [],
“l(fā)inks”: {…},
“included”: {…}
}