本文摘抄至 Google Cloud API 设计指南(见附录1),用于标注在实践该规范时一些关键注重点。(未完待续...)
简介
这是互联网 API 的通用设计指南。它自 2014 年起在 Google 内部使用,是 Google 在设计 Cloud API 和其他 Google API 时遵循的指南。我们在此公开此设计指南,目的是为外部开发者提供信息,使我们所有人都能更轻松地协同工作。
HTTP 准则
- HTTP 请求和响应主体使用
application/json
作为Content-Type
TODO 举例
- 长请求网址(GET/DELETE)
网址具有实际长度限制,通常在 2k-8k 之间,当使用的 API 网址超过长度限制时,可能会由于多方原因拒绝此请求。要避开该限制,客户端应设置Content-Type: application/x-www-form-urlencoded
的 POST 请求,且 HTTP 标头为X-HTTP-Method-Override: GET
TODO 举例
-
请求网址符合 REST 模型时,应将 HTTP 方法指定为 API 规范的一部分
安全方法不应表示检索以外的操作(如 GET 和 HEAD),不应对客户端产生任何的副作用。
HTTP 中的幂等性,意味着多个相同的请求产生的副作用与单个请求相同(GET/PUT/DELETE)
- 其他详情查看附录2
REST
什么是 REST API?
REST(Representational State Transfer) API 是可单独寻址的“资源”(API 中的“名词”)的“集合”。资源通过资源名称被引用,并通过一组“方法”(也称为“动词”或“操作”)进行控制。
资源
面向资源的 API 通常被构建为资源层次结构,其中每个节点是一个“简单资源”或“集合资源”。 为方便起见,它们通常被分别称为资源和集合。
- 一个集合包含相同类型的资源列表。 例如,一个用户拥有一组联系人。
- 资源具有一些状态和零个或多个子资源。 每个子资源可以是一个简单资源或一个集合资源。
资源名称
// 书架上的书
shelves/${shelf1}/books/${book2}
// 资源名
- 必须是复数形式的小驼峰(如果没有合适的复数形式,则使用单数形式)
- 必须使用简明扼要的英文词语
- 应该避免使用过于笼统的词语,或对其限定后使用(例如 rowValues 优先于 values),例如:elements/entries/instances/items/objects/resources/types/values
- 资源名需要网址转义(URLEncode)
- 资源名为字符串
- 推荐使用资源名代替资源 ID 来标识资源
方法
面向资源的 API 的关键特性是,强调资源(数据模型)甚于资源上执行的方法(功能)。典型的面向资源的 API 使用少量方法公开大量资源。方法可以是标准方法或自定义方法。对于本指南,标准方法有:List、Get、Create、Update 和 Delete
示例
Gmail API 服务实现了 Gmail API 并公开了大多数 Gmail 功能。它具有以下资源模型:
- API 服务:gmail.googleapis.com
- 用户集合:users/*。每个用户都拥有以下资源。
- 消息集合:users/*/messages/*。
- 线程集合:users/*/threads/*。
- 标签集合:users/*/labels/*。
- 变更历史记录集合:users/*/history/*。
- 表示用户个人资料的资源:users/*/profile。
- 表示用户设置的资源:users/*/settings。
标准方法
标准方法可降低复杂性并提高一致性
List(列表)
常用于搜索资源,适用于来自单个集合的数据,该集合大小有限且不进行缓存
HTTP 映射
- Method: GET
- 响应正文应该包含资源列表,以及可选元数据
- 接收资源名称的请求消息字段应该映射到网址路径(资源主体标识)
- 所有剩余的请求消息字段应该映射到网址查询参数(query params)
// 分页查询场景
GET shelves/${shelf1}/books?page=1&pageSize=10&pageToken={上一页的标识}
{
books: [...],
nextPageToken: "..." // 可缺省
}
// TODO(d): 无分页场景是否适用本规则?
常见状态码(HTTP)
- 200 资源获取成功
- 4xx 一些请求错误
- TODO
Get(获取)
返回指定资源
HTTP 映射
- Method: GET
- 响应正文应该包含资源列表,以及可选元数据
- 接收资源名称的请求消息字段应该映射到网址路径(资源主体标识)
- 所有剩余的请求消息字段应该映射到网址查询参数(query params)
GET shelves/${shelf1}/books/${encode('时间简史')}?author=zhangsan
// 整个返回体为 Book 实例
{
author: 'zhangsan',
content: 'xxx'
}
常见状态码(HTTP)
- 200 资源获取成功
- 404 找不到资源
- 4xx 一些请求错误
- TODO
Create(创建)
在指定父资源下创建新资源,并返回新创建的资源。
HTTP 映射
- Method: POST
- 所有剩余的请求消息字段应该映射到网址查询参数
POST shelves/${shelf1}/books
// 请求正文为即将创建的资源
{
name: 'xxx',
author: 'xxx'
}
// 响应正文
{
name: 'xxx',
author: 'xxx'
}
常见状态码(HTTP)
- 200 资源创建成功(对于标准的 RESTful API 来说,创建成功对应 201,但 Google 规范中,标准 POST 不再用于更新场景,所以不存在语义上的冲突)
- 4xx 一些请求错误
- TODO
Update(更新)
用于除了更新资源名或父资源属性之外,可以改变所有可变资源(重命名或移动应使用自定义方法)
HTTP 映射(标准)
Method: PATCH
- 支持部分资源更新,并设置更新部分的字段掩码
- 包含资源的请求消息字段必须映射到请求正文
- 所有剩余的请求消息字段必须映射到网址查询参数
- 响应消息必须是更新的资源本身
PATCH shelves/${shelf1}/books/${bookName}
// 请求正文
{
book: {
name: 'xxx'
},
updateMask: 'book.name'
}
// 响应正文
{
name: 'xxx',
author: 'xxx'
}
HTTP 映射(完整资源更新,不推荐,添加新字段时会存在向后兼容问题)
- Method: PUT
- 仅支持完整更新
- 包含资源的请求消息字段必须映射到请求正文
- 所有剩余的请求消息字段必须映射到网址查询参数
- 响应消息必须是更新的资源本身
PUT shelves/${shelf1}/books/${bookName}
// 请求正文
{
name: 'xxx', // 改变此字段
author: 'xxx' // 不变此字段
}
// 响应正文
{
name: 'xxx',
author: 'xxx'
}
常见状态码(HTTP)
- 200 更新成功
- 4xx 一些请求错误
- TODO
Delete(删除)
用于删除或计划删除指定资源,API 不应依赖于该方法返回任何信息,因为不能重复调用。
HTTP 映射(完整资源更新,不推荐,添加新字段时会存在向后兼容问题)
- Method: DELETE
- 接收资源名称的请求消息字段应该映射到网址路径
- 所有剩余的请求消息字段应该映射到网址查询参数
- 如果 Delete 方法立即移除资源,则应该返回空响应
- 如果 Delete 方法启动长时间运行的操作,则应该返回长时间运行的操作
- 如果 Delete 方法仅将资源标记为已删除,则应该返回更新后的资源
DELETE shelves/${shelf1}/books/${bookName}
常见状态码(HTTP)
- 200 删除成功
- 404 资源不存在
- 4xx 一些请求错误
- TODO
自定义方法
自定义方法是指 5 个标准方法之外的 API 方法。这些方法应该仅用于标准方法不易表达的功能。通常情况下,API 设计者应该尽可能优先考虑使用标准方法,而不是自定义方法。标准方法具有大多数开发者熟悉的更简单且定义明确的语义,因此更易于使用且不易出错。另一项优势是 API 平台更加了解和支持标准方法,例如计费、错误处理、日志记录、监控。
自定义方法可以与资源、集合或服务关联。 它可以接受任意请求和返回任意响应,并且还支持流式请求和响应。
https://service.name/v1/some/resource/name:customVerb
- 自定义方法应使用 HTTP POST,因为该动词具有更灵活的语义。(作为替代 GET 或 LIST 的方法,可以使用 GET)
- 自定义方法不应该使用 HTTP PATCH(根据具体语义而定)
常用自定义方法
错误
Google API 的错误模型由 google.rpc.Status 逻辑定义,该实例
在发生 API 错误时
返回给客户端
格式(rpc)
package google.rpc;
message Status {
// 客户端可以轻松处理的简单错误代码
int32 code = 1;
// 面向开发人员人类可读的错误信息,它应该解释错误,并提供一个可行的解决方案
string message = 2;
// 一些额外的错误信息,如重试信息或帮助链接
repeated google.protobuf.Any details = 3;
}
格式(http)
// resp headers
Content-Type: application/json
Status: 404
// resp body
{
"message": "NOT FOUND",
"details": "" // 可选
}
单个 API 应避免定义其他错误代码,因为开发人员不太可能编写用于处理大量错误代码的逻辑。作为参考,每个 API 调用平均处理 3 个错误代码意味着大多数应用逻辑仅用于错误处理,这并不会带来良好的开发者体验。
常见错误状态
命名规范
大规则:简单、直观、一致
- 采用美式英语(如:使用 license(美) 而非 licence(英))
- 可以使用已被广泛接受的简写
- 尽量使用常用词汇(如:描述移除和销毁,删除优于擦除)
- 避免名称过载。使用不同的名称命名不同的概念
- 避免过于笼统的命名
- 集合 ID 采用小驼峰复数形式(如:members)
设计模式
列表分页
可列表集合应该支持分页,即使结果通常很小
如果某个 API 从一开始就不支持分页,稍后再支持它就比较麻烦,因为添加分页会破坏 API 的行为。 不知道 API 正在使用分页的客户端可能会错误地认为他们收到了完整的结果,而实际上只收到了第一页
跨子集执行 List/Search 操作
有时,API 需要让客户跨子集执行 List/Search 操作。例如,“API 图书馆”有一组书架,每个书架都有一系列书籍,而客户希望在所有书架上搜索某一本书。在这种情况下,建议在子集合上使用标准 List,并为父集合指定通配符集合 ID "-"。对于“API 图书馆”示例,我们可以使用以下 REST API 请求:
GET https://xxx/v1/shelves/-/books?filter=xxx
枚举默认值:0
每个枚举定义必须以 0 值条目开头,当未明确指定枚举值时,应使用该条目。API 必须记录如何处理 0 值
部分响应(与 List/Search 区别?)
通过特殊字段(如 Google 的 FieldMask)给予过滤响应集合内容
资源视图
为了减少网络流量,有时可允许客户端限制服务器应在其响应中返回的资源部分,即返回资源视图而不是完整的资源表示形式。API 中的资源视图支持是通过向方法请求添加一个参数来实现的,该参数允许客户端指定希望在响应中接收的资源视图