1 RESTful
1.1 什么是RESTful
官方解释
Representational State Transfer 的简称,即 表现层状态转移。
人看的解释
- REST指一组架构约束条件和原则, 如果一个架构符合 REST 的约束条件和原则,就称之为 RESTful 架构
- RESTful 是一种软件架构风格,而不是标准
大神总结
- URL定位资源
- HTTP动词(GET, POST, DELETE, PUT)描述操作
- HTTP状态码表示响应结果
总的来说,RESTful是一种web服务设计风格,风格意思就是大家默认的但不是强制的。
1.2 REST规范
用URL定位资源
REST 的主体是资源,所谓“资源”,就是网络上的一个具体信息,例如:一张图片,一段文字、一种服务。总之就是一个实际存在的东西,而 URL 就是用来指向这个资源的。
例如:
https://api.example.com/users
用HTTP动词描述操作
对资源的操作,无外乎 CRUD(增删改查),RESTful 中,每个 HTTP 动词对应一个 CRUD 操作。
- GET:对应 Retrieve 操作。GET请求从不改变资源的状态。无副作用。GET方法是幂等的。GET方法具有只读的含义。因此,你可以完美的使用缓存。
- POST:对应 Create 操作
- DELETE:对应 Delete 操作
- PUT:对应 Update 操作
用HTTP状态码表示响应结果
RESTful Web服务应使用合适的HTTP状态码来响应客户端请求
- 2xx - 成功 - 一切都很好
- 4xx - 客户端错误 - 如果客户端发生错误(例如客户端发送无效请求或未被授权)
- 5xx – 服务器错误 - 如果服务器发生错误(例如,尝试处理请求时出错)
2 RESTful API设计实践
设计RESTful API可能很棘手,因为没有官方和强制标准。基本上,实现API的方法有很多种,但其中一些方法已在实践中得到证明并被广泛采用。这篇文章涵盖了构建 HTTP 和 RESTful API 的最佳实践。我们将讨论 URL 结构、HTTP 方法、创建和更新资源、设计关系、有效负载格式、分页、版本控制等等。
2.1 URL设计建议
用名词代替动词表示资源
这让你的API更简洁,URL数目更少
正确示例:
GET /employees
GET /employees?state=external
POST /employees
PUT /employees/56
错误示例:
/getAllEmployees
/getAllExternalEmployees
/createEmployee
/updateEmployee
资源用复数表示,全部小写,用_或-连接,不要使用驼峰命名法
这是个人爱好问题,但复数形式更为常见。最重要的是:避免复数和单数名词混合使用,这显得非常混乱且容易出错。
推荐:
/employees
/employees/21
不推荐:
/employee
/employee/21
每个资源使用两个URL
资源集合用一个URL,具体某个资源用一个URL
/employees #资源集合的URL
/employees/56 #具体某个资源的URL
在URL中强制加入版本号
从始至终,都使用版本号发布您的RESTful API。将版本号放在URL中以是必需的。如果您有不兼容和破坏性的更改,版本号将让你能更容易的发布API。发布新API时,只需在增加版本号中的数字。这样的话,客户端可以自如的迁移到新API,不会因调用完全不同的新API而陷入困境。使用直观的 “v” 前缀来表示后面的数字是版本号。
你不需要使用次级版本号(“v1.2”),因为你不应该频繁的去发布API版本。
正确示例:
/v1/employees
错误示例:
/v1.2/employees
提供分页信息
一次性返回数据库所有资源不是一个好主意。因此,需要提供分页机制。通常使用数据库中众所周知的参数offset和limit。
/employees?offset=30&limit=15 #返回30 到 45的员工
如果客户端没有传这些参数,则应使用默认值。通常默认值是offset = 0和limit = 10。如果数据库检索很慢,应当减小limit值
/employees #返回0 到 10的员工
此外,如果您使用分页,客户端需要知道资源总数。例: 请求:
GET /employees
响应:
{
"offset": 0,
"limit": 10,
"total": 3465,
"employees": [
//...
]
}
对可选的、复杂的参数,使用查询字符串(?)
为了让你的URL更小、更简洁。为资源设置一个基本URL,将可选的、复杂的参数用查询字符串表示。
正确示例:
GET /employees?state=internal&maturity=senior
错误示例:
GET /employees
GET /externalEmployees
GET /internalEmployees
GET /internalAndSeniorEmployees
非资源请求用动词
有时API调用并不涉及资源(如计算,翻译或转换)。
GET /translate?from=de_DE&to=en_US&text=Hallo
//Trigger an operation that changes the server-side state
POST /restartServer
//no body
POST /banUserFromChannel
{ "user": "123", "channel": "serious-chat-channel" }
在这种情况下,不涉及任何资源,服务器执行一个操作并将结果返回给客户端。因此,您应该在 URL 中使用动词而不是名词来清楚地区分操作(RPC-style APIs)和用于领域建模的资源(REST APIs)。
- 如果一个API已操作为主,应该设计成RPC风格
- 如果一个API主要用来对相关数据进行CRUD,应该设计成REST风格
2.2 HTTP动词设计建议
HTTP动词语义
- Get
- 幂等
- 只读。GET永远不会改变服务器端资源的状态,它必须没有副作用。
- PUT
- 幂等
- 可用于创建和更新
- 常用于更新(完全更新)。例如:PUT /employees/1- 更新员工 1
- 始终在请求中包含整个有效负载。要么全有要么全无。PUT不用于部分更新
- POST
- 非幂等
- 用于创建。例如:POST /employees创建一个新员工
- DELETE
- 幂等
- 用于删除。例如:DELETE /employees/1
- PATCH
- 幂等
- 用于部分更新。例如:PATCH /employees/1- 使用负载中包含的字段更新员工1, 员工1的其他字段不会被更新
2个URL乘以4个HTTP方法就是一组很好的功能。看看这个表格:
POST(创建) | DELETE(删除) | PUT(更新) | GET(查询) | |
---|---|---|---|---|
/employees | 创建一个新员工 | 删除所有员工 | 批量更新员工信息 | 列出所有员工 |
/employees/56 | (错误) | 删除56号员工 | 更新56号员工的信息 | 获取56号员工的信息 |
2.3 HTTP状态码设计建议
RESTful Web服务应使用合适的HTTP状态码来响应客户端请求,其中的大部分HTTP状态码都不会被用到,只会用其中的一小部分。通常会用到一下几个:
2xx:成功 | 3xx:重定向 | 4xx:客户端错误 | 5xx:服务器错误 |
---|---|---|---|
200 成功 | 301 永久重定向 | 400 错误请求 | 500 内部服务器错误 |
201 创建 | 304 资源未修改 | 401未授权 | |
403 禁止 | |||
404 未找到 |
2.4 响应内容建议
使用小驼峰命名法
使用小驼峰命名法作为属性标识符。
{ "yearOfBirth": 1982 }
不要使用下划线(year_of_birth)或大驼峰命名法(YearOfBirth)。通常,RESTful Web服务将被JavaScript编写的客户端使用。客户端会将JSON响应转换为JavaScript对象(通过调用var person = JSON.parse(response)),然后调用其属性。因此,最好遵循JavaScript代码通用规范
将实际数据包装在一个data字段中
GET响应,PUT、POST 和 PATCH 请求体都应该将实际数据包装data字段
- 有剩余空间可以添加元数据(例如用于分页、链接、弃用警告、错误消息)
- 保持数据一致性
- 与 JSON:API 标准兼容
GET /employees # 返回data字段中的对象列表
{
"data": [
{ "id": 1, "name": "Larry" }
, { "id": 2, "name": "Peter" }
]
}
GET /employees/1 # 返回data字段中的单个对象
{
"data": {
"id": 1,
"name": "Larry"
}
}
返回有用的错误信息
除了合适的状态码之外,还应该在HTTP响应正文中提供有用的错误提示和详细的描述。这是一个例子:
请求
GET /employees?state=super
响应
// 400 Bad Request
{
"errors": [
{
"status": 400,
"detail": "Invalid state. Valid values are 'internal' or 'external'",
"code": 352,
"links": {
"about": "http://www.domain.com/rest/errorcode/352"
}
}
]
}
提供用于导航的链接
理想情况下,您不要让您的客户构建 URL 以使用您的 REST API。让我们考虑一个例子。
客户想要访问员工的工资报表。因此,他必须知道他可以通过将查询参数附加salaryStatements到员工 URL(例如/employees/21/salaryStatements)来访问工资报表。这种字符串连接容易出错、脆弱且难以维护。如果您更改访问 REST API 中的薪水报表的方式(例如,现在使用“salary-statements”或“paySlips”),所有客户端都将中断。
{
"data": [
{
"id":1,
"name":"Paul",
"links": [
{
"salary": "http://www.domain.com/employees/1/salaryStatements"
}
]
}
]
}
如果客户端完全依赖链接来获取工资单,那么即使您更改 API,他也不会中断,因为客户端将始终获得有效的 URL(只要您在 URL 更改的情况下更新链接)。另一个好处是您的 API 变得更具自我描述性,并且客户不必经常查找文档。
适当的设计关系
让我们假设每个employee都有 amanager和几个teamMembers。在API中设计关系基本上有三个常用方式:链接、外键和嵌入。
它们都是有效的,正确的选择取决于用例。主要考虑
- 客户端的访问模式
- 可容忍的请求次数
- 有效负载大小
链接:负载小,但要获取完整数据访问次数过多
{
"data": [
{
"id": 1,
"name": "Larry",
"relationships": {
"manager": "http://www.domain.com/employees/1/manager",
"teamMembers": [
"http://www.domain.com/employees/12",
"http://www.domain.com/employees/13"
]
//or "teamMembers": "http://www.domain.com/employees/1/teamMembers"
}
}
]
}
外键:负载大小适中,没有重复,但用户需要解析数据,使用麻烦
{
"data": [
{
"id": 1,
"name": "Larry",
"relationships": {
"manager": 5 ,
"teamMembers": [ 12, 13 ]
}
}
],
"included": {
"manager": {
"id": 5,
"name": "Kevin"
},
"teamMembers": [
{ "id": 12, "name": "Albert" }
, { "id": 13, "name": "Tom" }
]
}
}
嵌入:用户获取数据最方便,但负载过大,引用数据会重复
{
"data": [
{
"id": 1,
"name": "Larry",
"manager": {
"id": 5,
"name": "Kevin"
},
"teamMembers": [
{ "id": 12, "name": "Albert" }
, { "id": 13, "name": "Tom" }
]
}
]
}