## Rest API 規范
Fielding是HTTP協議(1.0版和1.1版)的主要設計者、Apache服務器軟件的作者之一、Apache基金會的第一任主席。Fielding將他對互聯網軟件的架構原則,定名為 REST,即 Representational State Transfer 的縮寫。如果一個架構符合 REST 原則,就稱它為RESTful架構。
## RESTful API Design 名詞定義
Resource: 一個簡單的實例。有一些屬性或者一些子資源,子資源可以是 一個簡單的資源或者一組資源例如:book, userCollection: 一組同類的資源對象。例如:books, usersHTTP: 網絡協議。
## HTTP Verbs
* GET(SELECT):從服務器取出資源(一項或多項)
* POST(CREATE):在服務器新建一個資源
* PUT(UPDATE):在服務器更新完整的資源(客戶端提供改變后的完整資源)
* DELETE(DELETE):從服務器刪除資源
## Resource Oriented Design
### 設計流程
確定一個api提供什么類型的資源確定資源之間的依賴關系基于類型和依賴關系確定資源的命名確定資源的結構為資源添加最少的方法。
### Resource Names
資源是一個實體對象,那么資源名就是這個資源的標識。
一個資源名應該由Resource ID,Collection ID 和API Service 名組成。
例1:存儲服務有 buckets 的集合,其中每個桶包含一個 objects 集合。
| API Service Name | Collection ID | Resource ID | Sub-Collection ID | Sub-Resource ID |
| --- | --- | --- | --- | --- |
| [//gateway.com.cn/storage](https://gateway.com.cn/storage) | /buckets | /bucket-id | /objects | /object-id |
全名:[//gateway.com.cn/storage/buckets/1/materials/wood](https://gateway.com.cn/storage/buckets/1/materials/wood)
相對名:buckets/1/materials/woodHTTP
URL:[http://gateway.com.cn/storage/v1/buckets/1/materials/wood](http://gateway.com.cn/storage/v1/buckets/1/materials/wood)
### Resource ID
Resource ID 標識著資源屬于父資源中。Resource ID 可能不止一個單詞,也有可能是一個相對路徑。Resource ID 必須清楚地被記錄,無論是客戶端、服務器或第三方。
### Collection ID
Collection ID 必須是有效的程序標識符。Collection ID 必須是駝峰形式的復數結構,如果沒有復數形式,應使用單數。Collection ID 必須是清晰簡潔的英文單詞。Collection ID 避免使用籠統的表示,例如objects、values、types。
## Action 命名規范
### 基本規范
使用”/“表示層級關系url 不能以”/“結尾url 中不能包含空格””url 中不能以文件后綴結尾url 中字母小寫,單詞間加下劃線不要再url中添加CRUD
### 類別
| Description | Action Name | HTTP Mapping | HTTP Request Body | HTTP Response Body |
| --- | --- | --- | --- | --- |
| 查詢所有 | list | GET | N/A | Resource\*list |
| 獲取單個資源 | query | GET | N/A | Resource\* |
| 創建單個資源 | create | POST | Resource | Resource\* |
| 更新單個資源 | update | PUT | Resource | Resource\* |
| 刪除單個資源 | delete | DELETE | N/A | Empty |
### List
List 方法接受一個 Collection id 和0或多個參數作為輸入,并返回一個列表的資源。
List 必須使用 GET 方法接口必須以 collection id 結尾。其他的請求字段必須作為 url 查詢參數。沒有請求體,接口上必須不能包含request body。響應體應該包含一組資源和一些可選項元數據。
### Query
Query 方法接受一個 Resource name 和0或多個參數,并返回指定的資源。
Query 必須使 GET 方法。請求url 必須包含 Resource name。其他的請求字段必須作為 url 查詢參數。沒有請求體,接口上必須不能包含request body。響應體應該返回整個資源對象。
### Create
Create 方法接受一個 Collection id ,一個資源,和0或多個參數。它創建一個新的資源在指定的父資源下,并返回新創建的資源。
Create 必須使用 POST 方法。應該包含父資源名用于指定資源創建的位置。創建的資源應該對應在request body。響應體應該返回整個資源對象。Create 必須傳遞一個resource,這樣即使resource 的結構發生變化,也不需要去更新方法或者資源結構,那些棄用的字段則需要標識為“只讀”。
### Update
Update 方法接受一個資源和0或多個參數。更新指定的資源和其屬性,并返回更新的資源。
除了Resource Name 和其父資源之外,這個資源的所有屬性應該是可以更新的。資源的重命名和移動則需要自定義方法。如果只支持一個完整對象的更新,Update 必須使用 PUT 方法。Resource Name必須包含在請求的url中。資源應該對應在request body。
### Delete
Delete 方法接受一個Resource Name 和0或多個參數,并刪除指定的資源。
Delete 必須使用 DELETE 方法。Resource Name 必須包含在請求的url中。沒有請求體,接口上必須不能包含request body。如果是立即刪除,應該返回空如果是啟動一個刪除操作,應該返回一個刪除操作。如果只是標識某個資源是“被刪除的”,應該返回一個更新后的資源。如果多個刪除請求刪除同一資源,那么只有第一個請求才應該成功,其他的返回not found。
## 自定義方法
自定義的方法應該參考5個基本方法。應該用于基本方法不能實現的功能性方法。可能需要一個任意請求并返回一個任意的響應,也可能是流媒體請求和響應。
可以對應a resource, a collection 甚至 a service。
自定義方法應該使用 POST 方法。不應該使用PATCH 方法。自定義方法對應的 Resource Name 或者 Collection id 必須包含在請求的url中。如果使用的HTTP 方法接受request body,則需要對應一個請求體。如果使用的HTTP 方法不接受request body,則需要聲明不使用body,并且參數應該作為url查詢參數。
## 批量添加
| Description | Action Name | HTTP Mapping | HTTP Request Body | HTTP Response Body |
| --- | --- | --- | --- | --- |
| 批量添加 | batchCreate | POST/batch\_create | Resource\*list | Resource IDS |
## 批量刪除
| Description | Action Name | HTTP Mapping | HTTP Request Body | HTTP Response Body |
| --- | --- | --- | --- | --- |
| 批量刪除 | batchDelete | POST /batch\_delete | Resource IDS | Empty |
## 更新單個資源中的屬性
| Description | Action Name | HTTP Mapping | HTTP Request Body | HTTP Response Body |
| --- | --- | --- | --- | --- |
| 更新資源的狀態 | updateAttribute | POST /:attribute?value= | N/A | {“key”:“”,“value”:“”} |
| 更新用戶的年齡 | updateAge | POST /v1/users/1/age?value=20 | N/A | {“key”:“age”,“value”:“20”} |
## 對資源執行某一動作
比如發送消息,啟用什么功能。如果是針對資源,則Action Name為動詞。如果是針對資源的屬性,則Action Name為動詞+屬性名。請求以動詞結尾,屬性作為參數。
| Description | Action Name | HTTP Mapping | HTTP Request Body | HTTP Response Body |
| --- | --- | --- | --- | --- |
| 對資源執行某一動作 | customVerb | POST /custom\_verb | N/A | \* |
| 取消某種操作 | cancel | POST /cancel | N/A | Boolean |
| 從回收站中恢復一個資源 | undelete | POST /v1/projects/1/undelete | N/A | Boolean |
| 檢查項目是否重名 | checkName | POST /v1/projects/1/check?name= | N/A | |
## 查詢某一資源的單個屬性
對于單個資源的所有的查詢Action Name,都需要以query開頭。Action Name以query+屬性名結尾。
| Description | Action Name | HTTP Mapping | HTTP Request Body | HTTP Response Body |
| --- | --- | --- | --- | --- |
| 查詢資源的某屬性 | queryAttribute | GET /:attribute | N/A | {“key”:“”,“value”:“”} |
| 查詢用戶的年齡 | queryAge | GET /v1/users/1/age | N/A | {“key”:“age”,“value”:“25”} |
| 查詢用戶下的項目 | queryProjects | GET /v1/users/1/projects | N/A | {“key”:“projects”,“value”:\[\]} |
## 查詢collection 的數量
計算集合自身的數量,使用count作為Action Name計算資源中子集合的數量,使用count+集合名作為Action Name。
| Description | Action Name | HTTP Mapping | HTTP Request Body | HTTP Response Body |
| --- | --- | --- | --- | --- |
| 查詢Collection 的數量 | count | GET /count | N/A | {“key”:“”,“count”:“”} |
| 查詢組織的數目 | count | GET /v1/organizations/count | N/A | {“key”:“organizations”,“count”:“100”} |
| 查詢用戶下的所有項目數量 | countProjects | GET /v1/users/1/projects/count | N/A | {“key”:“projects”,“count”:“100”} |
## 復雜條件查詢
對于collection的所有查詢Action Name,都需要以list開頭。查詢的條件中,如果條件為一到兩個,使用By和And。
eg.: listByUserIdAndName如果查詢條件大于3個,則使用ByOptions,查詢條件作為請求體傳入。
eg.: listByOptions
## 版本控制
主版本號必須作為包名的最后一個字符。如:cn.exrick.controller.v1。
版本兼容的修改:
添加一個服務接口添加一個api方法添加一個請求字段添加一個相應字段添加一個字段的枚舉值
版本不兼容的修改:
刪除或重命名一個服務、接口、方法,枚舉值改變一種HTTP method改變字段的類型改變一個resource name
## Demo示例
~~~
@RestController("/v1/users")
public class UserController {
@GetMapping("/")
public ResponseEntity<User> list() {
return null;
}
@GetMapping("/{id}")
public User query(@PathVariable("id") String id) {
return null;
}
@PostMapping("/")
public Result<User> create(@RequestBody User user) {
return null;
}
@PutMapping("/{id}")
public Result<User> update(@PathVariable("id") String id, @RequestBody User user) {
return null;
}
@DeleteMapping("/{id}")
public Result<User> delete(@PathVariable("id") String id) {
return null;
}
@PostMapping("/batch_create")
public Result<User> batchCreate(@RequestBody List<User> users) {
return null;
}
@PostMapping("/batch_delete")
public Result<User> batchDelete(@RequestBody List<User> users) {
return null;
}
@PostMapping("/age")
public Result<User> updateAge(@RequestParam("value") Integer age) {
return null;
}
@PostMapping("/{:id}/undelete")
public Result<User> undelete(@PathVariable("id") String id) {
return null;
}
@PostMapping("/check")
public Result<User> checkName(@RequestParam("name") String name) {
return null;
}
@GetMapping("/{:id}/age")
public Result<User> queryAge(@PathVariable("id") String id) {
return null;
}
@GetMapping("/{:id}/name")
public Result<User> queryByUserIdAndName(@PathVariable("id") String id, @RequestParam("name") String name) {
return null;
}
@GetMapping("/{:id}/projects/count")
public Result<User> countProjects(@PathVariable("id") String id, @RequestParam("name") String name) {
return null;
}
@GetMapping("/")
public Result<User> listByOptions(@RequestBody Map<String, Object> options) {
return null;
}
}
~~~