Google REST API 设计关键总结

本文摘抄至 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)


    image.png
  • 其他详情查看附录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。

标准方法

标准方法可降低复杂性并提高一致性


image.png

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(根据具体语义而定)

常用自定义方法

image.png

错误

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 个错误代码意味着大多数应用逻辑仅用于错误处理,这并不会带来良好的开发者体验。

常见错误状态

image.png

命名规范

大规则:简单、直观、一致

  • 采用美式英语(如:使用 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 中的资源视图支持是通过向方法请求添加一个参数来实现的,该参数允许客户端指定希望在响应中接收的资源视图

TODO

附录

  1. Google Cloud API 设计指南

  2. HTTP 准则详细

  3. Google API 错误模型详情

  4. 当前文稿原始地址

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 去年有段时间得空,就把谷歌GAE的API权威指南看了一遍,收获颇丰,特别是在自己几乎独立开发了公司的云数据中心之后...
    骑单车的勋爵阅读 20,433评论 0 41
  • 标签(空格分隔): google restful api design 当前版本的API设计指南发布时间:2017...
    主君_05c4阅读 3,887评论 0 5
  • Java继承关系初始化顺序 父类的静态变量-->父类的静态代码块-->子类的静态变量-->子类的静态代码快-->父...
    第六象限阅读 2,143评论 0 9
  • 欢迎关注公众号“Tim在路上” 1.听说你对JVM有点研究,讲一讲JVM的内存模型吧(我说虚拟机栈,本地方法栈,程...
    Tim在路上阅读 3,515评论 4 91
  • 前言:如果你有更好的私藏文章,不凡分享出来,独乐乐不如众乐乐(⊙o⊙) 本文总结了 RESTful API 设计相...
    utopia84阅读 4,013评论 2 35