Restful API
1 HTTP Method
几乎所有的主流网络协议都有两个部分,一个是协议头,一个是协议体。协议头中是协议自己要用的数据,协议体才是用户的数据。所以,协议头主要是用于协议的控制逻辑,而协议体则是业务逻辑。HTTP的动词(或是Method)是在协议头中,所以,其主要用于控制逻辑。
| 方法 | 描述 | 幂等 |
|---|---|---|
| GET | 用于查询操作,对应于数据库的 select 操作 | ✔︎ |
| PUT | 用于所有的信息更新,对应于数据库的 update 操作 | ✔︎︎ |
| DELETE | 用于更新操作,对应于数据库的 delete 操作 | ✔︎︎ |
| POST | 用于新增操作,对应于数据库的 insert 操作 | ✘ |
| HEAD | 用于返回一个资源对象的“元数据”,或是用于探测API是否健康 | ✔︎ |
| PATCH | 用于局部信息的更新,对应于数据库的 update 操作 | ✘ |
| OPTIONS | 获取API的相关的信息。 | ✔︎ |
幂等性是数学中的一个概念,表达的是N次变换与1次变换的结果相同。HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。
1.1 常见需求:
- 缓存。通过CDN或是网关对API进行缓存,很显然,我们要在查询
GET操作上建议缓存。 - 流控。你可以通过HTTP的动词进行更粒度的流控,比如:限制API的请用频率,在读操作上和写操作上应该是不一样的。
- 路由。比如:写请求路由到写服务上,读请求路由到读服务上。
- 权限。可以获得更细粒度的权限控制和审计。
- 监控。因为不同的方法的API的性能都不一样,所以,可以区分做性能分析。
- 压测。当你需要压力测试API时,如果没有动词的区分的话,我相信你的压力测试很难搞吧。
1.2 各种方法适合返回的状态码
| Status Code | 200 Success | 201 Created | 202 Accepted | 204 No Content | 400 Bad Request | 404 Not Found | 422 Unprocessable Entity | 500 Internal Server Error |
|---|---|---|---|---|---|---|---|---|
GET | X | X | X | X | X | |||
POST | X | X | X | X | X | X | X | |
PUT | X | X | X | X | X | X | X | |
PATCH | X | X | X | X | X | X | ||
DELETE | X | X | X | X | X | X |
2 API Design Guidelines
2.1 URI Naming Conventions
https://example.com:8042/over/there?name=ferret#nose
\___/ \_______________/\_________/\_________/\__/
| | | | |
scheme authority path query fragment
| Term | Defiition |
|---|---|
| URI | [end-point] '/' resource-path ['?'query] |
| end-point | [ scheme '://' ] [ host [':' port]] |
| scheme | "http" or "https" |
| resource-path | "/v" version '/' namespace-name '/' resource ('/' resource) |
| resource | resource-name ['/' resource-id] |
| resource-name | Alpha (Alpha | Digit | '-')* |
| resource-id | value |
| query | name '=' value ('&' name = value)* |
| name | Alpha (Alpha | Digit | '_')* |
| value | URI Percent encoded value |
- URI 只用小写字母
- URI path 使用短横线 - 作为分隔符
- URI query string 使用下划线 _ 作为分隔符
- 对于返回多个数据,URI path 应当使用复数名词,如:/books,/users
- 对于单个数据,可存在于多个数据之下,如:/books/book-id
- 单个数据之下,可以存在相关联的多个数据,如:/users/user-id/books
- 出现两级多个数据与单个数据对应关系时,更好的方式是缩减为一级对应关系,如二级关系:/users/user-id/books/book-id,更好的方式:/user-books/user-book-id
- URI path resource-name 只使用名词,不使用动词
- 单个数据用单数名词:/user,多个数据用复数名词:/users
- 返回数据均为JSON类型,键名为小写字符且用下划线 _ 作为分隔符,键值只能为:objects, strings, numbers, booleans, or arrays of objects,且数组对应的键名应为复数名词,表示数组可能有多个元素
- 对于枚举类型的值,只使用大写字符和下划线 _ 作为分隔符
- URI query string 只用于限制返回的数据集,如:过滤、排序,搜索等等,因此资源标识符应当放在URI中,而不能放在 query string 中。如应该用:/books/book-id,而不是:/books?book_id=book_id
- 基于上一条,所以对于返回单个数据的 API,是不能用 query string 的
- 对于需要缓存的数据,可以使用 GET + query string 取代 POST + body ,因为后者不能缓存
- 对于同一个 query string 有多个值时,有两种方案可选:?status=CLOSED&status=INVALID,或者 ?statuses=CLOSED,INVALID。对于前者服务器需要能够解析同一字段的多个值,对于后者服务器要按照逗号分隔符自己提取多个参数。一般建议使用前者,因为HTTP本身就支持。
3 JSON Schema
Json Schema 定义了一套词汇和规则,这套词汇和规则用来定义 Json 元数据,且元数据也是通过 Json 数据形式表达的。Json 元数据定义了 Json 数据需要满足的规范,规范包括成员、结构、类型、约束等。一般使用 Json Schema 来进行 json 数据格式验证,在数据提交到业务层次之前进行 json 格式的验证。
// 假设有一个 API,接受一个json请求,返回某个用户在某个城市关系最近的若干个好友。该 API 需要定义什么样的请求数据是合法的,那就用 JSON Schema
// JSON data
{
"city" : "chicago",
"number": 20,
"user" : {
"name":"Alex",
"age":20
}
}
// JSON Schema
{
"type": "object",
"properties": {
"city": {"type": "string" },
"number": {"type": "number" },
"user": {
"type": "object",
"properties": {
"name" : {"type": "string"},
"age" : {"type": "number"}
}
}
}
}
4 Restful 复杂查询
一般来说,对于查询类的API,主要就是要完成四种操作:排序,过滤,搜索,分页。下面是一些相关的规范。
- 排序。对于结果集的排序,使用
sort关键字,以及{field_name}|{asc|desc},{field_name}|{asc|desc}的相关语法。比如,某API需要返回公司的列表,并按照某些字段排序,如:GET /admin/companies?sort=rank|asc或是GET /admin/companies?sort=rank|asc,zip_code|desc - 过滤。对于结果集的过滤,使用
filter关键字,以及{field_name} op{value}的语法。比如:GET /companies?category=banking&location=china。但是有些时候,我们需要更为灵活的表达式,我们就需要在URL上构造我们的表达式。这里需要定义六个比较操作:=,<,>,<=,>=,以及三个逻辑操作:and,or,not。(表达式中的一些特殊字符需要做一定的转义,比如:>=转成ge)于是,我们就会有如下的查询表达式:GET /products?$filter=name eq 'Milk' and price lt 2.55查找所有的小于2.55的牛奶。 - 搜索。对于相关的搜索,使用
search关键字,以及关键词。如:GET /books/search?description=algorithm或是直接就是全文搜索GET /books/search?key=algorithm。 - 分页。对于结果集进行分页处理,分页必需是一个默认行为,这样不会产生大量的返回数据。使用
page和per_page代表页码和每页数据量,比如:GET /books?page=3&per_page=20。 - 可选。上面提到的
page方式为使用相对位置来获取数据,可能会存在两个问题:性能(大数据量)与数据偏差(高频更新)。此时可以使用绝对位置来获取数据:事先记录下当前已获取数据里最后一条数据的ID、时间等信息,以此获取 “该ID之前的数据” 或 “该时刻之前的数据”。示例:GET /news?max_id=23454345&per_page=20或GET /news?published_before=2011-01-01T00:00:00Z&per_page=20。