1 REST 之上 HTTP 知识
在构建良好的 REST API 之前,我们了解一些 HTTP 协议的基础知识。
但就 REST API 设计本身,所涉及到的 HTTP 知识要点包含以下几条:
- HTTP 中包含动词(或方法): GET、POST、PUT、DELETE 是最常用的。
- REST 是面向资源的,一个资源被一个 URI 所标识,如 /users/。
- 端点(endpoint),一般指动词与 URI 的组合,如 GET: /users/。
- 一个端点可以被解释为对某种资源进行的某个动作。如 POST: /articles 可能代表“创建一个新的 article”。
- 在业务领域,可以将动词和 CRUD(增删查改)关联起来:GET 代表查,POST代表增,PUT 代表改,DELETE 代表删。
2 资源的定义
面向资源的 API 通常被构建为资源层次结构,其中每个节点是一个“简单资源”或“集合资源”。
一个集合资源包含相同类型的资源列表。 例如,一个用户拥有一组联系人。
资源具有一些状态和零个或多个子资源。 每个子资源可以是一个简单资源或一个集合资源。
3 URI定义
a、用名词代替动词表示资源
使用名词会让你的 API 更简洁,URL 数目更少,请看如下示例。
#不要这么设计
/getAllUsers
/getAllExternalUsers
/createUser
/updateUser
#更好的设计
GET /users
GET /users?state=external
POST /users
PUT /users/56
b、(不要出现动词作为资源)用 HTTP 方法操作资源
使用 URL 指定你要用的资源。使用 HTTP 方法来指定怎么处理这个资源。使用四种 HTTP 方法 POST、GET、PUT、DELETE 可以提供 CRUD 功能(创建,获取,更新,删除)。
- 获取:使用 GET 方法获取资源。GET 请求从不改变资源的状态。无副作用。GET 方法是幂等的。GET 方法具有只读的含义。
- 创建:使用 POST 创建新的资源。
- 更新:使用 PUT 更新现有资源。
- 删除:使用 DELETE 删除现有资源。
- 当 HTTP 提供的 GET、DELETE、POST、PUT 方法不够表示出接口能力含义时,会需要自定义动作(参考表格:自定义方法)。
2 个 URL 乘以 4 个 HTTP 方法就是一组很好的功能。
POST(创建)
GET(读取)
PUT(更新)
DELETE(删除)
标准方法 | HTTP 方法 | 举例描述 | 举例示例 |
---|---|---|---|
get | GET | 单个资源查询 | /v1.0/users/{user_id} |
list | GET | 资源分页、列表查询 | /v1.0/users?page_no=1&page_size=10 |
create | POST | 资源创建 | /v1.0/users |
update | PUT | 资源修改 | /v1.0/users/{user_id} |
delete | DELETE | 资源删除 | /v1.0/users/{user_id} |
自定义方法 | POST | 自定义不要出现动作为资源 | /v1.0/msgs/{msg_id}/actions-push |
GET 准则
- 返回的资源对象名称必须映射到网址中
- 请求参数应该映射到 query 参数中
- 不可包含body
PUT 准则
- 返回的资源对象名称必须映射到网址中
- 包含资源的请求必须映射到 body 中,其余放到 query 中
- 响应消息必须是资源本身
- 必须保证幂等
DELETE 准则
- 返回的资源对象名称必须映射到网址中
- 请求参数应该映射到 query 参数中
- 不可包含 body
- 物理删除应该返回空,逻辑删除应该返回删除后状态
POST 准则
- 返回的资源对象名称必须映射到网址中
- 请求参数应该映射到 query 参数中
- 请求可以包含 resource_id 字段,允许客户端定义
- 响应消息必须是资源本身
- 可以使用名词-动词来自定义方法
Restful API实践总结
1、推荐使用复数名词
尽量使用复数,但是避免复数和单数名词混合使用,这显得非常混乱且容易出错。
#推荐
/users
/users/21
#不推荐
/user
/user/21
2、对可选的、复杂的参数,使用查询字符串(?)
#不推荐:
GET /users
GET /external_users
GET /internal_users
GET /internal_and_seniorusers
#推荐(为了让 URL 更小、更简洁。为资源设置一个基本 URL,将可选的、复杂的参数用查询字符串表示)
GET /users?state=internal&maturity=senior
3、为关系使用子资源
常见的情况下,资源需要多级分类,因此很容易写出多级的 URL,如下有一些正例和反例。
#正例(获取已发布的文章)
GET /articles?published=true
#反例(获取已发布的文章)
GET /articles/published
#正例
#Returns a list of drivers for car 711
GET /cars/711/drivers
#Returns driver #4 for car 711
GET /cars/711/drivers/4
4、使用用简明扼要的英文
#正例
/v1.0/config
#反例
/v1.0/configuration
5、必须使用中划线 - 连接组合词
#正例
/v1.0/users-password/1
#如下都是反例
/v1.0/userspassword/1/
/v1.0/usersPassword/1/
/v1.0/users password/1/
6、为集合提供过滤
为所有字段或者查询语句提供独立的查询参数。
#Returns a list of red cars
GET /cars?color=red
#Returns a list of cars with a maximum of 2 seats
GET /cars?seats<=2
7、为集合提供排序
允许跨越多字段的正序或者倒序排列:
这种更易读(参考了各大厂风格)
GET /cars?sort=name:desc,model:asc
8、为集合提供字段选择
一些情况下,只需要在列表中查询几个有标识意义的字段,不需要服务端把所有字段的值都请求出来,需要支持 API 选择查询字段的能力,也可提高网络传输性能和速度。
GET /cars?fields=manufacturer,model,id,color
9、为集合提供分页
提供两种分页查询实践。
---
范例1(普通分页,如MySQL数据源)
---
#入参
page_no Integer 页码
page_size Integer 分页大小
[其他查询条件参数...]
#出参
has_more Boolean 是否有下一页
total Long 总数
list List<T> 数据集合
---
范例2(滚动分页,如ES数据源)
---
#入参
page_size Integer 分页大小
last_row_key String 上一页返回的分页标识
[其他查询条件参数...]
#出参
has_more Boolean 是否有下一页
total Long 总数
last_row_key String 上一页返回的分页标识
list List<T> 数据集合
10、入参放uri,还是url后面?
#如果是资源主体,资源递进关系,放 uri 中
GET:/v1.0/users/{user_id}
#如果是对资源有一个筛选能力,放 query 中
GET:/v1.0/users?user_ids=xxx
11、uri 正例和反例示例
#示例1(获取设备列表)
//正例
GET:/v1.0/devices?product_id=xxx
//反例(查询必须用 GET)
POST:/v1.0/devices
BODY:{"productId":"xxx"}
//反例(属于资源主体属性的查询过滤条件不要体现在路径内,建议放到 query 中)
GET:/v1.0/devices/products/{product_id}
#示例2(修改设备信息)
//正例
PUT:/v1.0/devices/{device_id}
//反例(修改必须用 PUT)
POST:/v1.0/devices/{device_id}
BODY:{}
//反例(资源尽量用复数表示)
PUT:/v1.0/device/{device_id}
#示例3(修改app群组分组定时)
//正例
POST:/v1.0/app-groups/modify-timers
BODY {"app_group_id":"","category":"","group_id":""}
//反例(资源层级最好不要超过两级;语义复杂的建议使用 POST)
PUT:/v1.0/app-groups/{app_group_id}/timers/categories/{category}/groups/{group_id}/status
#示例3(触发app上报)
//正例
POST:/v1.0/apps/{app_id}/trigger-report
//反例(自定义语义用 POST 来表示; {动词-名词} 格式来表示。)
PUT:/v1.0/apps/{app_id}/trigger-report
POST:/v1.0/apps/{app_id}/report/trigger