├── .gitignore ├── API-design-guide ├── 01-简介.md ├── 02-面向资源的设计.md ├── 03-资源命名.md ├── 04-标准方法.md ├── 05-自定义方法.md ├── 06-标准字段.md ├── 07-错误.md ├── 08-命名约定.md ├── 09-一般设计模式.md ├── 10-文档.md ├── 11-使用Proto3.md ├── 12-版本控制.md ├── 13-兼容性.md ├── 14-目录结构.md ├── 15-文件结构.md ├── 16-HTTP 方法.md ├── 17-术语表.md └── README.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | -------------------------------------------------------------------------------- /API-design-guide/01-简介.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | 这是一份网络API的通用设计指南。Google内部从2014年开始使用它,我们在设计[Cloud API](https://cloud.google.com/apis/docs/overview)和[Google API](https://github.com/googleapis/googleapis)时也遵从了这份指南。我们把它分享给Google以外的开发人员,这也会让我们的合作变得更顺利。 4 | 5 | 外部开发者可能会发现在设计使用[Google Cloud Endpoint](https://cloud.google.com/endpoints/docs/grpc)(译者注:GCE,面向开发者的api管理平台)的API时这份指南非常有用,我们强烈建议外部开发者使用这些设计准则。尽管如此,我们并不强求任何非Google的开发人员使用这份指南,你可以在使用Cloud Endpoint或者gRPC时不遵从这份指南。 6 | 7 | 这份指南既适用于REST API也适用于RPC API,并重点关注gRPC API。gRPC API使用[Protocol Buffer](https://cloud.google.com/apis/design/proto3)定义API接口,并使用[API Service Configuration](https://github.com/googleapis/googleapis)去配置API服务,包括HTTP映射,日志和监控。Google API和gRPC Cloud Endpoint API使用HTTP映射特性用把JSON/HTTP[转码](https://cloud.google.com/endpoints/docs/transcoding)到Protocal Buffer和RPC。 8 | 9 | 这份指南会随着时间的变化持续更新,去采用和适应新风格和设计模式。本着这个原则,这份指南从来不会处于完备状态,API的设计艺术和技艺总是有提升的空间。 10 | 11 | # 本文档的约定 12 | 13 | 本文档所用的需求级别描述词语"MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL"在[RFC 2119](https://www.ietf.org/rfc/rfc2119.txt)中有详细描述,在文档中这些关键词使用**粗体**显示。 14 | -------------------------------------------------------------------------------- /API-design-guide/02-面向资源的设计.md: -------------------------------------------------------------------------------- 1 | # 面向资源的设计 2 | 3 | 这份设计指南的目标是帮助开发人员设计**简单、一致、易用**的网络API。同时,它也有助于收敛基于socket的API和(注:原文是with,这里翻译为“和”)基于HTTP的REST API。 4 | 5 | 以前,人们根据诸如CORBA和Windows COM这样的API接口和方法设计RPC API。随着时间的推移,接口和方法越来越多。最后,接口和方法数不胜数又各不相同。开发人员要正确使用它们,必须仔细了解每一个的用法,这很浪费时间而且容易出错。 6 | 7 | 2000年,为了与HTTP1.1搭配使用,[REST](https://en.wikipedia.org/wiki/Representational_state_transfer)架构风格出现。它的核心原则是定义用少量方法就能操作的命名资源。资源和方法可视为API的*名词*和*动词*。在HTTP协议中,资源名称自然地对应于URL,方法则对应于HTTP的`POST`、`GET`、`PUT`、`PATCH`和`DELETE`方法。 8 | 9 | 在互联网领域,HTTP REST API最近获得了巨大的成功。截至2010年,大约74%的公网API都是HTTP REST API。 10 | 11 | 尽管HTTP REST API在互联网领域已经很流行了,但其承载的流量还是比传统的RPC API小。比如,在高峰期,美国大约一半的互联网流量都是视频内容,出于性能上的考虑,很少有人用REST API。在数据中心内部,更多的公司也使用基于socket的RPC API来承载大多数网络流量,这比REST API的数量高出几个量级。 12 | 13 | 事实上,RPC API和HTTP REST API都有存在的理由。在理想情况下,API平台最好提供这两种API。这份设计指南也是基于这一原则帮你设计和构建API。在通用API设计上,本指南应用面向资源设计的原则,定义了众多常见的设计模式以提高易用性并降低复杂性。 14 | 15 | *注意*:本设计指南解释了如何将REST原则用于独立于编程语言,操作系统或者网络协议的API设计,它不只是用于创建REST API的指南。 16 | 17 | ## 什么是REST API 18 | 一组REST API被建模为*一组*可独立寻址的*资源*(API的*名词*)。资源被通过[资源名称](https://cloud.google.com/apis/design/resource_names)被引用,通过一小组*方法*(也被称为*动词*或者*操作*)被操作。 19 | 20 | Google REST API(也被称为*REST方法*)的*标准方法*有`List`,`Get`,`Create`,`Update`和`Delete`。API的设计者也可以用自定义方法(也被称为*自定义动词*或者*自定义操作*),来完成那些不易对应到标准方法的功能,比如数据库事务。 21 | 22 | **注意**:自定义动词不意味着创建自定义的HTTP动词以支持自定义方法。对于基于HTTP的API来说,自定义动词被对应到最合适的HTTP动词上。(译者注:不能自创HTTP动词,应该使用含义最接近的HTTP动词去设计API) 23 | 24 | ## 设计流程 25 | 26 | 设计指南建议使用以下步骤设计面向资源的API(更多细节请参考下面指定章节): 27 | - 确定一个API提供什么类型的资源 28 | - 确定资源之间的关系 29 | - 根据资源类型和关系确定资源命名方案 30 | - 明确资源schema 31 | - 给资源添加最少的方法集 32 | 33 | 34 | ## 资源 35 | 36 | 面向资源的API通常被建模为一个资源层次结构。其中每一个节点可以是一个*简单资源*也可以是*一组资源*。为了方便,它们通常被称为资源或者资源组。 37 | 38 | - 资源组包含**相同类型**的一系列资源。比如,一个用户有一组联系人。 39 | - 资源有相同的状态,也有零或者多个子资源。每一个子资源可以是单个资源也可是一组资源。 40 | 41 | 例如,Gmail API有一组用户,每个用户有一组消息,一组主题,一组标签,单个用户配置资源或多个设置项资源。 42 | 43 | 尽管REST API和存储系统有概念上的一致性,但具有面向资源的API的服务不一定是数据库,并且在如何解释资源和方法上有巨大的灵活性。比如,创建一个日历事件(资源)可能会为参会者创建附加事件,向参会者发送邀请邮件,预约会议室,更新视频会议日程表。 44 | 45 | ## 方法 46 | 面向资源的API的关键特性是强调资源(数据模型)和(译者注:原文看起来是笔误,这里应该翻译为和)运行在资源上的方法(功能)。典型的面向资源的API通过少量方法的暴露大量资源。这些方法可以是标准的或者自定义的。在本指南中,标准方法是指:`List`,`Get`,`Create`,`Update`和`Delete`。 47 | 48 | 当API功能自然地映射到一个标准方法时,这个方法**应当**用于API设计。若当功能不能自然的映射到标准方法时,**可以**使用*自定义方法*。[自定义方法](05-自定义方法.md)能提供与传统RPC API一样的设计自由度,可以用来实现常见的编程模式,比如数据库事务或者数据分析。 49 | 50 | ## 例子 51 | 接下来,我们看一看现实中如何使用面向资源的API来设计大规模服务。 52 | 53 | ### Gmail API 54 | Gmail API服务实现了Gmail API,暴露了绝大多数Gmail的功能。它的资源模型如下: 55 | 56 | - Gmail API服务:`gmail.googleapis.com` 57 | - 一组用户:`users/*`。每个用户有如下资源。 58 | - 一组消息:`users/*/messages/*`。 59 | - 一组主题:`users/*/threads/*`。 60 | - 一组标签:`users/*/labels/*`。 61 | - 一组变更历史:`users/*/history/*`。 62 | - 一个代表用户资料的资源:`users/*/profile` 63 | - 一个代表用户配置的资源:`users/*/settings` 64 | 65 | ### Google Cloud Pub/Sub API 66 | 67 | `pubsub.googleapis.com`服务实现了Goole Cloud Pub/Sub API,它定义了以下资源模型: 68 | - API服务:`pubsub.googleapis.com` 69 | - 一组主题:`projects/*/topics/*`。 70 | - 一组订阅:`projects/*/subscriptions/*`。 71 | 72 | **注意**:PUB/SUB API的其他实现可能选用了与上面不同的命名规则。 73 | -------------------------------------------------------------------------------- /API-design-guide/03-资源命名.md: -------------------------------------------------------------------------------- 1 | # 资源命名 2 | 3 | 在面向资源的API中,资源是命名实体,资源名称是资源的标识符。每个资源必须有其唯一的资源名称。资源名称由资源ID本身,父资源的ID和资源对应的API服务名称组成。在下文,我们将探讨资源ID和如何构建资源名称。 4 | 5 | gRPC API应该使用无模式的[URIs](http://tools.ietf.org/html/rfc3986)作为资源名称。它们通常遵循REST URL的惯例并且表现得更像网络文件路径。它们可以轻松的映射到REST URL上:细节请参考下一节[标准方法](04-标准方法.md)。 6 | 7 | 资源组是一种特殊的资源,它包含一组相同类型的子资源。例如,一个目录是一组文件资源。组的资源ID被称为组ID。 8 | 9 | 资源名称使用组ID和资源ID分层组织,以斜杠(译者注:/,下同)分割。如果某个资源包含子资源,子资源的名称为:父资源名称,斜杠,子资源ID。 10 | 11 | 例1:一个存储服务有一组`bucket`,每一个bucket有一组`objects`: 12 | 13 | 14 | | API资源名称 | 组ID | 资源ID | 组ID | 资源ID | 15 | | ------------------------ | -------- | --------- | ------- | --------- | 16 | | //storage.googleapis.com | /buckets | bucket-id | objects | object-id | 17 | 18 | 例2:一个邮件服务有一组`user`,每个user有子资源`settings`,settings还有子资源`customFrom` 19 | 20 | | API资源名称 | 组ID | 资源ID | 组ID | 资源ID | 21 | | --------------------- | ------ | ---------------- | -------- | ---------- | 22 | | //mail.googleapis.com | /users | name@example.com | settings | customFrom | 23 | 24 | API的提供者可以选择任何可接受的值作为资源和资源组的ID,只要它们在资源的层次结构中是唯一的即可。关于选择合适的资源ID和资源组ID,下文还有更多的参考。 25 | 26 | 只要资源名称的每段都不包含斜杠,通过分割资源名称可以获取独立的资源组ID和资源ID,比如`name.split("/")[n]`。 27 | 28 | ## 资源全名 29 | 规则松散的URI由DNS兼容的API服务名称和资源路径组成。资源路径也称为相对资源名称。 30 | 31 | > "//library.googleapis.com/shelves/shelf1/books/book2" 32 | 33 | API服务名称用于定位API服务端;它也**可以**是仅供内部的服务的伪DNS名称(译者注:API服务名可能用于外部调用,也可能只用于内部调用)。如果API服务名称在上下文中比较明显,相对资源名称则比较常用。 34 | 35 | ## 相对资源名称 36 | 没有前导“/”的URI路径([路径-无模式](http://tools.ietf.org/html/rfc3986#appendix-A)),标识API服务中的一个资源。比如: 37 | > "shelves/shelf1/books/book2" 38 | 39 | ## 资源ID 40 | 一个非空的URI片段([segment-nz-nc](http://tools.ietf.org/html/rfc3986#appendix-A))标识父资源中的资源。比如上例。 41 | 42 | 资源名称中的资源ID后缀**可以**有不止一个URI片段。比如: 43 | 44 | 45 | | 资源组ID | 资源ID | 46 | | -------- | -------------------- | 47 | | files | /source/py/parser.py | 48 | 49 | API服务**应当**尽可能的使用URL友好的资源ID。资源ID**必须**清楚的描述,不管它是由客户端、服务器端还是其他方指定。 50 | 51 | ## 资源组ID 52 | 一个非空的URI片段([segment-nz-nc](http://tools.ietf.org/html/rfc3986#appendix-A))标识父资源中的资源组,比如上例。 53 | 54 | 因为资源组ID经常出现在生成的客户端库里,它们**必须**符合以下要求: 55 | - **必须**是有效的C/C++标识符。 56 | - **必须**是驼峰命名的复数形式;首字母小写。 57 | - **必须**使用清晰简明的英文词语。 58 | - **应当**避免或者限定过于笼统的词语。比如,RowValue优于Value。没有限定的情况下**应当**避免以下词语: 59 | - Element 60 | - Entry 61 | - Instance 62 | - Item 63 | - Object 64 | - Resource 65 | - Type 66 | - Value 67 | 68 | ## 资源名称 vs URL 69 | 虽然完整的资源名称类似于普通URL,但他们不是一回事。单个资源可以由不同版本的API和不同API的协议暴露出来。完整资源名称没有指定API的协议和版本,在实际使用中,它**必须**被映射到特定的协议和API版本(译者注:完整资源名称)。 70 | 71 | 为了通过REST API使用资源的全名,**必须**在服务名称前添加HTTP协议,在资源路径前添加API主要版本号以及对资源路径进行URL转义,将其转换为REST URL。比如: 72 | 73 | > //这是一个日历事件资源名称 74 | > 75 | >"//calendar.googleapis.com/users/john smith/events/123" 76 | > 77 | > 这是对应的HTTP URL 78 | > 79 | > "https://calendar.googleapis.com/v3/users/john%20smith/events/123" 80 | 81 | ## 作为字符串的资源名称 82 | Google API**必须**使用字符串表示资源名称,除非向后兼容性有问题。资源名称**应当**像正常文件路径那样处理,并且他们不支持%编码。 83 | 84 | 对于资源定义来说,资源名称的第一个字段**应当**是字符串字段,并被命名为**Name**。 85 | 86 | **注意**:其他名称相关的字段**应当**具备避免混淆的命名,比如`display\_name`, `first\_name`, `last\_name`, `full\_name`。 87 | 88 | 例如: 89 | ``` 90 | service LibraryService { 91 | rpc GetBook(GetBookRequest) returns (Book) { 92 | option (google.api.http) = { 93 | get: "/v1/{name=shelves/*/books/*}" 94 | }; 95 | }; 96 | rpc CreateBook(CreateBookRequest) returns (Book) { 97 | option (google.api.http) = { 98 | post: "/v1/{parent=shelves/*}/books" 99 | body: "book" 100 | }; 101 | }; 102 | } 103 | 104 | message Book { 105 | //书的资源名称。格式必须是:"shelves/*/books/" 106 | //比如:"shelves/shelf1/books/book2"。 107 | string name = 1; 108 | 109 | // ... 其他属性 110 | } 111 | 112 | message GetBookRequest { 113 | //书的资源名称。"shelves/shelf1/books/book2"。 114 | string name = 1; 115 | } 116 | 117 | message CreateBookRequest { 118 | // 新建书的父资源的资源名称 119 | // 比如"shelves/shelf1". 120 | string parent = 1; 121 | // 要创建的书籍资源,客户端绝不能设置‘Book.name’属性 122 | Book book = 2; 123 | } 124 | ``` 125 | **注意**:为了资源名称的统一,开头的斜杠**绝不能**让URL模板变量捕获。例如,**必须**使用URL模板"/v1/{name=shelves/\*/books/\*}"而不能使用"/v1{name=/shelves/\*/books/\*}". 126 | -------------------------------------------------------------------------------- /API-design-guide/04-标准方法.md: -------------------------------------------------------------------------------- 1 | # 标准方法 2 | 3 | 本章阐述标准方法的概念,包括了`List`, `Get`, `Create`, `Update`, and `Delete`。很多不同类型的API都拥有非常类似的语义,把它们归纳为标准方法能够显著降低复杂度并提高一致性。在[谷歌API](https://github.com/googleapis/googleapis)仓库中,超过70%的API属于标准方法。标准方法更容易学习和使用。 4 | 5 | 以下表格描述了如何将标准方法映射为REST方法,也就是所谓的CRUD方法: 6 | 7 | | 方法 | HTTP 方法映射 | HTTP 请求体 | HTTP 返回体 | 8 | | ----------------- | -------------------------- | ----------- | ----------- | 9 | | [List](#list) | **GET <集合URL>** | 空 | 资源\* 列表 | 10 | | [Get](#get) | **GET <资源URL>** | 空 | 资源\* | 11 | | [Create](#create) | **POST <集合URL>** | 资源 | 资源\* | 12 | | [Update](#update) | **PUT or PATCH <资源URL>** | 资源 | 资源\* | 13 | | [Delete](#delete) | **DELETE <资源URL>** | 空 | 空\*\* | 14 | 15 | \*如果方法支持字段掩码并指定要返回字段的子集时,,从`List`,`Get`,`Create`和`Update`方法返回的资源**可能**包含部分数据。在一些情况下,API平台的所有方法都支持字段掩码。 16 | 17 | \*\*Delete方法如果并没有立刻删除响应的资源(例如创建一个耗时删除操作或者更新标识),它的响应**应该**包括耗时操作(译者注:耗时操作可看做是对服务器端长时间运行过程的抽象。因为运行过程耗时长。为了不阻塞客户端同时给客户端跟踪运行状况的机会,可以先给调用方返回一个对象,这个对象对应服务器端的执行过程,可以用它获取远程操作的状态和结果)或更新后的资源。 18 | 19 | 如果请求无法在单个API调用时间段内完成时,标准方法**可以**返回一个[耗时操作](https://github.com/googleapis/googleapis/blob/master/google/longrunning/operations.proto)。 20 | 21 | 以下章节描述了各标准方法的细节。范例中使用 .proto 文件定义方法,HTTP映射则通过特殊注释表明。你会发现[Google APIs](https://github.com/googleapis/googleapis)仓库中有很多使用标准方法的案例。 22 | 23 | ## List 24 | 25 | `List`方法接受一个集合名,零或者多个参数,根据输入返回相应的资源列表。它也经常被用作搜索资源。 26 | 27 | `List`适用于量有限且无缓存的单一集合数据查询;若需要更广的应用,**应该**[用自定义方法](05-自定义方法.md)`Search`。 28 | 29 | 批量获取(如接受多个资源ID并返回每个ID对象的方法)应该使用自定义方法`BatchGet`实现,而不是`List`方法。但如果你已经有了提供相似功能的`List`方法,你也**可以**继续使用。如果你使用自定义的`BatchGet`方法,**应该**确保它映射为HTTP GET方法。 30 | 31 | 使用常见模式:[分页](09-通用设计模式.md#列表分页),[结果排序](09-通用设计模式.md#排序顺序)。 32 | 33 | 适用命名约定:[过滤字段](08-命名约定.md#列表过滤器字段),[结果字段](08-命名约定.md#列表响应)。 34 | 35 | ### HTTP 映射 36 | 37 | * List方法**必须**使用HTTP `Get`方法。 38 | * 请求消息字段接收资源名称集合,而相关资源**应该**映射为URL路径。如果集合名称映射到URL路径,URL模板中的最后一段(即集合ID)**必须**为文字。 39 | * 其他所有请求消息字段**应当**映射为URL请求参数(译者注:大意是资源名字段映射到URL路径,其他映射到请求参数,参考[统一资源定位符](https://zh.wikipedia.org/wiki/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E5%AE%9A%E4%BD%8D%E7%AC%A6))中。 40 | * 没有请求体,即API配置中**不应该**声明请求体。 41 | * 返回体**应该**包含资源集合以及可选的元数据。 42 | 43 | ```go 44 | // 列举给定书架上的所有书 45 | rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) { 46 | // List方法映射为HTTP GET。 47 | option (google.api.http) = { 48 | // 在`parent`指定父级资源名,如"shelves/shelf1"。 49 | get: "/v1/{parent=shelves/*}/books" 50 | }; 51 | } 52 | 53 | message ListBooksRequest { 54 | // 父级资源名,如"shelves/shelf1" 55 | string parent = 1; 56 | 57 | // 返回的最大数据条数 58 | int32 page_size = 2; 59 | 60 | // 上一次List请求返回的next_page_token值,如果有的话 61 | string page_token = 3; 62 | } 63 | 64 | message ListBooksResponse { 65 | // 字段名必须跟方法名中的"books"一致 66 | // 数据返回的最大条数由请求中的page_size属性值制定 67 | repeated Book books = 1; 68 | 69 | // 获取下一页数据的令牌,如果没有更多数据则为空. 70 | string next_page_token = 2; 71 | } 72 | ``` 73 | 74 | ## Get 75 | 76 | `Get`方法接受一个资源名,零到多个参数,返回指定的资源。 77 | 78 | ### HTTP 映射 79 | 80 | * `Get`方法**必须**使用HTTP Get方法。 81 | * 接收资源名称的请求消息字段(可多个)**应该**映射到URL路径中。 82 | * 其他所有请求消息字段**应当**映射到URL查询参数中。 83 | * 无请求体,即API配置中**绝对不可以**出现请求体声明。 84 | * 返回资源**应当**映射到返回体中。 85 | 86 | ```go 87 | // 获得指定书籍 88 | rpc GetBook(GetBookRequest) returns (Book) { 89 | // Get映射为HTTP GET,资源名绑定到URL中,无请求体 90 | option (google.api.http) = { 91 | // 注意URL模板中有多个片段包括多个变量,以指定书籍相应的不同资源名,例如: 92 | // "shelves/shelf1/books/book2" 93 | get: "/v1/{name=shelves/*/books/*}" 94 | }; 95 | } 96 | 97 | message GetBookRequest { 98 | // 请求的资源名,如: 99 | // "shelves/shelf1/books/book2" 100 | string name = 1; 101 | } 102 | ``` 103 | 104 | ## Create 105 | 106 | `Create`方法接受一个集合名,一个资源,并且有零或多个参数;然后在相应集合中创建新的资源,最后返回新创建的资源。 107 | 108 | 如果API支持创建资源,那么它**应该**有`Create`方法用于创建各种类型的资源。 109 | 110 | ### HTTP 映射 111 | 112 | * `Create`方法**必须**使用HTTP POST方法。 113 | * 请求消息**应该**有一个名为`parent`的字段,以接受父级资源名,当前资源将在父资源下创建。 114 | * 其他所有请求消息字段**应当**映射到URL查询参数中。 115 | * 请求**可以**包括一个名为\_id的字段,以允许调用方选择客户端分配的ID(译者注:Create方法创建的资源id可以是客户端生成的);此字段必须映射为URL查询参数。 116 | * 包含资源的请求消息字段**应该**映射到请求体中,如果HTTP子句用于Create方法,则必须使用:\的表单。 117 | * 返回的资源**应当**映射到整个返回体中。 118 | 119 | 120 | 如果`Create`方法支持客户端指定资源名,并且相应资源已经存在;那么它**应该**返回错误(**推荐**使用google.rpc.Code.ALREADY_EXISTS错误代码),或使用其它服务器指定的资源名:文档中需要清晰说明被创建的资源名可能跟传入的资源名不同。 121 | 122 | ```go 123 | rpc CreateBook(CreateBookRequest) returns (Book) { 124 | // Create映射为HTTP POST,集合名映射到URL路径 125 | // HTTP请求体包含资源 126 | option (google.api.http) = { 127 | // 在`parent`指定父级资源名,如"shelves/shelf1"。 128 | post: "/v1/{parent=shelves/*}/books" 129 | body: "book" 130 | }; 131 | } 132 | 133 | message CreateBookRequest { 134 | // 待创建book资源所属的父级资源名。 135 | string parent = 1; 136 | 137 | // 此书的ID 138 | string book_id = 3; 139 | 140 | // 待创建的book资源 141 | // 字段名必须跟方法名中名词一致 142 | Book book = 2; 143 | } 144 | 145 | rpc CreateShelf(CreateShelfRequest) returns (Shelf) { 146 | option (google.api.http) = { 147 | post: "/v1/shelves" 148 | body: "shelf" 149 | }; 150 | } 151 | 152 | message CreateShelfRequest { 153 | Shelf shelf = 1; 154 | } 155 | ``` 156 | 157 | ## Update 158 | 159 | `Update`方法接受包括一个资源的请求消息,并且有零或多个参数。它更新相应资源以及它的属性;返回更新后的资源。 160 | 161 | 可变的资源属性**应当**被`Update`方法修改,除非属性包含资源的名称或者父资源。所有的重命名或者移动资源操作**一定不能**用`Update`方法,这些**应当**用自定义方法处理。 162 | 163 | ### HTTP 映射 164 | 165 | * 标准的`Update`方法**应该**支持部分资源更新,并使用 HTTP `PATCH`方法以及名为`update_mask`的`FieldMask`字段。 166 | * 如果`Update`方法需要更高级的修复语义,比方说给重复字段增补新值,那么**应该**使用[自定义方法](05-自定义方法.md)。 167 | * 如果`Update`方法仅支持完整的资源更新,它**必须**使用HTTP `PUT`;但是强烈不推荐这么做,因为这会造成添加新资源字段时的兼容性问题。 168 | * 接受资源名的字段**必须**映射到URL路径中;字段也**可以**包含在资源消息中。 169 | * 包含资源的请求消息中字段**必须**映射到请求体中。 170 | * 其他所有请求消息字段**必须**映射到URL查询参数中。 171 | * 返回的结果*必须*是更新后的资源。 172 | 173 | 如果API允许客户端指定资源名,服务器**可以**允许客户端指定一个不存在的资源名并创建新的资源。否则,使用不存在的资源名时`Update`方法应该报错。如果不存在资源是唯一的错误条件,那么错误码**应该**用`NOT_FOUND`。 174 | 175 | API如果有`Update`方法,并且支持资源创建的话,就应该提供`Create`方法;以避免调用者误以为`Update`方法是创建资源的唯一方式。 176 | 177 | ```go 178 | rpc UpdateBook(UpdateBookRequest) returns (Book) { 179 | // Update 映射为HTTP PATCH。资源名映射到URL路径。 180 | // HTTP请求提包含资源 181 | option (google.api.http) = { 182 | // 注意URL模板中的变量指定了待更新的book资源名 183 | patch: "/v1/{book.name=shelves/*/books/*}" 184 | body: "book" 185 | }; 186 | } 187 | 188 | message UpdateBookRequest { 189 | // 用于更新服务器上资源的book数据 190 | Book book = 1; 191 | 192 | // 用于更新资源的掩码 193 | FieldMask update_mask = 2; 194 | } 195 | ``` 196 | 197 | ## Delete 198 | 199 | `Delete`方法接受一个资源名,零或多个参数;然后删除,或者安排删除相应的资源。`Delete`方法**应该**返回`google.protobuf.Empty`。 200 | 201 | 注意API**不应该**依赖于`Delete`方法返回的任何信息,因为它**不能**被反复调用(译者注:因为资源可能已经被删除了)。 202 | 203 | ### HTTP 映射 204 | 205 | * `Delete`方法**必须**使用HTTP `DELETE`方法。 206 | * 对应于资源名称的请求消息字段(可多个)**应该**绑定到URL路径中。 207 | * 其他所有请求消息字段**应当**映射到URL查询参数中。 208 | * 无请求体,即API配置中**绝对不可以**出现请求体声明。 209 | * 如果`Delete`方法立刻删除除资源,它**应该**返回空。 210 | * 如果`Delete`方法开启了一个耗时操作,它**应该**返回这个耗时操作(译者注:本节开始的译者注中提到了耗时操作)。 211 | * 如果`Delete`方法仅是把资源标记为删除,它需要返回更新后的资源 212 | 213 | 调用`Delete`方法必须是幂等的,但返回值可以不同。任意次数的`Delete`请求应当使得一个资源(最终)被删除,但只有第一次请求获得成功的返回值,后续的请求应当返回`google.rpc.Code.NOT_FOUND`. 214 | 215 | ```go 216 | rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) { 217 | // Delete 映射为HTTP DELETE方法,资源名绑定到URL路径中。 218 | // 没有请求体。 219 | option (google.api.http) = { 220 | // 注意URL模板中有多个片段包括多个变量,以指定待删除书籍相应的不同资源名,例如: 221 | // "shelves/shelf1/books/book2" 222 | delete: "/v1/{name=shelves/*/books/*}" 223 | }; 224 | } 225 | 226 | message DeleteBookRequest { 227 | // 等待删除的book数据资源名,如: 228 | // "shelves/shelf1/books/book2" 229 | string name = 1; 230 | } 231 | ``` 232 | -------------------------------------------------------------------------------- /API-design-guide/05-自定义方法.md: -------------------------------------------------------------------------------- 1 | # 自定义方法 2 | 3 | 本章将会讨论如何在API设计中使用自定义方法。 4 | 5 | 自定义方法指的是五个标准方法之外的API方法。他们**应当**仅用于标准方法不易表达的功能。一般而言,API设计者**应当**尽可能优先考虑使用标准方法,而不是自定义方法。标准方法相对更简单,定义完善的语义,并且开发者也更加熟悉;这使标准方法更易用,并且使用者更难犯错。使用标准方法的另一个优势是API平台会有更好的支持,如计费、错误处理、日志、监控等等。 6 | 7 | 自定义方法可以跟资源、集合或者服务关联。它**可以**接受任意请求,并返回任意结果;并支持流式的请求与结果。 8 | 9 | ## HTTP映射 10 | 11 | 对于自定义方法,它们**应该**使用以下形式的HTTP映射 12 | 13 | https://service.name/v1/some/resource/name:customVerb 14 | 15 | 使用`:`而不是`/`符号去分隔自定义动词跟资源名,可以让我们支持任意路径。比方说取消删除一个文件可以映射为`POST /files/a/long/file/name:undelete` 16 | 17 | 选择HTTP映射时,**应当**应用以下准则: 18 | 19 | * 自定义方法**应该**使用HTTP POST,因为它含有最灵活的语义。 20 | * 自定义方法**可以**使用其它HTTP动词(参考以下章节),但方法需要遵从该动词的标准[HTTP语义](https://tools.ietf.org/html/rfc2616#section-9)。 21 | * 请注意,使用HTTP `GET`的自定义方法**必须**是幂等并且无副作用。比方说,支持资源特定视图的自定义方法**可以**用HTTP `GET`。 22 | * 对应于资源名或者资源集合名的请求消息字段**应当**映射为URL路径 23 | * URL路径必须以冒号 + 自定义动词结尾 24 | * 如果HTTP动词允许请求体消息(如 `POST`, `PUT`, `PATCH`, 或者其它HTTP动词),那么自定义方法的HTTP配置**必须**使用`body: "\*"`,并且其它所有剩余的请求消息自动**应当**映射到请求体中。(译者注:TODO) 25 | * 如果HTTP动词不允许请求体消息(如 `GET`, `DELETE`),那么自定义方法的HTTP配置就**绝不可以**使用`body`,并且其它所有剩余的请求消息字段自动**应当**映射到URL查询参数中。 26 | 27 | **警告**:如果一个服务实现了多套API,API的生产者**必须**谨慎的创建服务配置,以避免API之间的自定义动词相互冲突。 28 | 29 | ```go 30 | // 这是一个服务级别的自定义方法。 31 | rpc Watch(WatchRequest) returns (WatchResponse) { 32 | // 自定义方法映射到HTTP POST。所有请求参数通过body传递。 33 | option (google.api.http) = { 34 | post: "/v1:watch" 35 | body: "*" 36 | }; 37 | } 38 | 39 | // 这是一个资源集合级别的自定义方法。 40 | rpc ClearEvents(ClearEventsRequest) returns (ClearEventsResponse) { 41 | option (google.api.http) = { 42 | post: "/v3/events:clear" 43 | body: "*" 44 | }; 45 | } 46 | 47 | // 这是一个资源级别的自定义方法。 48 | rpc CancelEvent(CancelEventRequest) returns (CancelEventResponse) { 49 | option (google.api.http) = { 50 | post: "/v3/{name=events/*}:cancel" 51 | body: "*" 52 | }; 53 | } 54 | 55 | // 这是批量获取的自定义方法。 56 | rpc BatchGetEvents(BatchGetEventsRequest) returns (BatchGetEventsResponse) { 57 | // 批量获取方法映射到HTTP GET 58 | option (google.api.http) = { 59 | get: "/v3/events:batchGet" 60 | }; 61 | } 62 | ``` 63 | 64 | ## 使用场景 65 | 66 | 某些场景下,自定义方法是合理的选择: 67 | 68 | * **重启一台虚拟机** 另外一种实现方式是:"在重启集合中创建一个新的重启资源",这让人感觉繁冗的过份。又或者是"虚拟机有客户端能从运行中跟重启中切换的可变状态"?这又会让人困惑是否还其他潜在的状态改变。重启是一个被广泛接受的概念,直接将其设为一个自定义方法,是符合开发者预期的。 69 | * **发送邮件** 创建一封电子邮件并不意味着要发送它,也可以仅是存为草稿。比较起其它的设计方式(把邮件移动到"发件箱"集合中),自定义方法的优势是更容易被API使用者察觉,建模也更直接。 70 | * **提拔一位同事** (corpeng) 如果使用标准Update方法,客户端需要复制企业提拔流程中的管理政策,以确保提拔是在正确的等级,并属于同一职业阶梯等等。 71 | * **批处理的方法**对于性能关键方法,可以提供自定义批处理方法以减少每次请求开销。例如,[accounts.locations.batchget](https://developers.google.com/my-business/reference/rest/v3/accounts.locations/batchGet)。 72 | 73 | 有的时候,标准方法则比自定义方法更适用: 74 | 75 | * 使用不同的参数查询资源 (使用标准`list`方法,跟标准list过滤)。 76 | * 简单资源属性修改(使用`update`方法跟字段掩码)。 77 | * 关闭一个通知(使用标准`delete`方法)。 78 | 79 | ## 常见自定义方法 80 | 81 | API设计者**应当**考虑使用以下一些常见或者有用的自定义方法名;而不是直接定义新的名字,以提高不同API之间的一致性。 82 | 83 | | 方法名 | 自定义动词 | HTTP动词 | 备注 | 84 | | -------- | ------------- | -------- | -------------------------------------------------- | 85 | | Cancel | **:cancel** | **POST** | 取消一个未完成的操作(构建,计算等等) | 86 | | BatchGet | **:batchGet** | **GET** | 批量获取多个资源(查阅`List`标准方法中的详细描述) | 87 | | Move | **:move** | **POST** | 将一个资源从一个父级移到另一个 | 88 | | Search | **:search** | **GET** | `List`的语义不足够时,搜索获取数据 | 89 | | Undelete | **:undelete** | **POST** | 恢复之前删除的数据;推荐的数据的保留时间是30天。 | 90 | -------------------------------------------------------------------------------- /API-design-guide/06-标准字段.md: -------------------------------------------------------------------------------- 1 | # 标准字段 2 | 3 | 本节描述了在需要类似概念时应使用的一组标准消息字段定义。 这将确保相同的概念在不同的API上具有相同的名称和语义。 4 | 5 | | 字段名 | 类型 | 描述 | 6 | | --------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 7 | | **name** | **string** | **name**字段应该包含[相对资源名](03-资源命名.md) | 8 | | **parent** | **string** | 对于资源定义和`List`/`Create`请求,**parent**字段应包含父级[相对资源名](03-资源命名.md) | 9 | | **create\_time** | **Timestamp** | 一个实体的创建时间戳 | 10 | | **update\_time** | **Timestamp** | 一个实体的最后更新时间戳;注意update_time会被create/patch/delete等操作更新 | 11 | | **delete\_time** | **Timestamp** | 实体的删除时间戳,仅当支持保留时。 | 12 | | **time\_zone** | **string** | 时区名,它应该符合[IANA时区](http://www.iana.org/time-zones)标准,如"America/Los_Angeles"。 有关详细信息,请参阅 https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. | 13 | | **region\_code** | **string** | 位置的Unicode国家/地区代码(CLDR),例如“US”和“419”。 有关详细信息,请参阅 http://www.unicode.org/reports/tr35/#unicode_region_subtag。 | 14 | | **language\_code** | **string** | BCP-47语言代码,如“en-US”或“sr-Latn”。 有关详细信息,请参阅http://www.unicode.org/reports/tr35/#Unicode_locale_identifier。 | 15 | | **display\_name** | **string** | 一个实体显示的名称。 | 16 | | **title** | **string** | 实体的正式名称,例如公司名称。 它应该被视为正规版本的display\_name | 17 | | **description** | **string** | 一个实体的详细文字描述 | 18 | | **filter** | **string** | `List`方法的标准过滤参数 | 19 | | **query** | **string** | 应用于`Search`方法的(也就是说 `:search`)**过滤**参数 | 20 | | **page\_token** | **string** | `List`请求的数据分页令牌 | 21 | | **page\_size** | **int32** | `List`请求的数据分页大小 | 22 | | **total\_size** | **int32** | 列表中的总条目数,不考虑分页 | 23 | | **next\_page\_token** | **string** | `List`返回结果中下一个分页的令牌。它应该在后续请求中传递为page_token参数;空值意味着没有更多数据 | 24 | | **request\_id** | **string** | 用于检测重复请求的唯一字符串id | 25 | | **resume\_token** | **string** | 用于恢复流式传输请求的隐含令牌 | 26 | | **labels** | **map\** | 表示云资源的标签 | 27 | | **deleted** | **bool** | 如果资源允许取消删除,则它必须有**deleted**字段表示资源是否已被删除 | 28 | | **show\_deleted** | **bool** | 如果资源允许取消删除,相应的`List`方法必须有一个**show_deleted**字段,以便客户端发现已删除的资源。 | 29 | | **validate_only** | **bool** | 如果为true,则表示给定的请求仅需要被检验,而不是被执行。 | 30 | -------------------------------------------------------------------------------- /API-design-guide/07-错误.md: -------------------------------------------------------------------------------- 1 | # 错误 2 | 3 | 本章概述了Google API错误模型,以及开发人员如何正确生成和处理错误的一般指南。 4 | 5 | Google API使用简单的协议无关错误模型,这使我们能够在不同的API,API协议(如gRPC或HTTP)以及错误上下文(例如,异步,批处理或工作流错误)中获得一致的体验。 6 | 7 | ## 错误模型 8 | 9 | 错误模型在逻辑上由[`google.rpc.Status`](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto)定义,当API发生错误时,返回一个Status实例给客户端。 以下代码段显示了错误模型的总体设计: 10 | 11 | ``` 12 | package google.rpc; 13 | 14 | message Status { 15 | // A simple error code that can be easily handled by the client. The 16 | // actual error code is defined by `google.rpc.Code`. 17 | int32 code = 1; 18 | 19 | // A developer-facing human-readable error message in English. It should 20 | // both explain the error and offer an actionable resolution to it. 21 | string message = 2; 22 | 23 | // Additional error information that the client code can use to handle 24 | // the error, such as retry delay or a help link. 25 | repeated google.protobuf.Any details = 3; 26 | } 27 | ``` 28 | 29 | 由于大多数Google API都使用面向资源的API设计,因此遵循相同的错误处理设计原则(即用一小组标准错误处理有大量资源)。 例如,服务器使用一个标准的`google.rpc.Code.NOT_FOUND`错误代码,而不是定义不同类型的“not found”错误,并告诉客户端找不到特定资源。 较小的状态空间降低了文档的复杂性,也在客户端库中提供更好的惯用映射,并降低客户端逻辑复杂性,而且没有限制包含的操作信息。 30 | 31 | ## 错误代码 32 | 33 | Google API**必须**使用[`google.rpc.Code`](https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto)定义的规范错误代码。 因为开发人员不太可能编写大量处理逻辑错误的代码,所以单独的API**应该**避免定义额外的错误代码。 作为参考,每个API调用平均处理3个错误就意味着大多数应用程序只是处理错误了,这对开发者来说体验不够友好。 34 | 35 | ## 错误消息 36 | 37 | 错误消息应该帮助用户轻松,快速地**理解和解决**API错误。 一般来说,在编写错误消息时,请考虑以下准则: 38 | 39 | * 不要假定用户是API专家。 用户可以是客户端开发人员,操作人员,IT人员或应用程序的最终用户。 40 | * 不要假定用户了解服务实现或熟悉错误的上下文(如日志分析)。 41 | * 如果可能,应构造错误消息,以便技术用户(但不一定是您的API开发人员)可以响应错误并进行更正。 42 | * 保持错误消息简练。 如果需要请提供链接,这样困惑的读者可以提出问题,提供反馈或获取更多信息(这些信息不一定适合在错误消息中展示)。如果不合适,就可以使用详细信息字段展开。 43 | 44 | ## 错误细节 45 | 46 | Google API为错误详细信息定义了一组标准错误有效内容,您可以在[`google/rpc/error_details.proto`](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto)中找到它。它涵盖了API错误的最常见需求,例如配额失败和无效参数。与错误代码一样,错误详细信息应尽可能使用这些标准有效内容。 47 | 48 | 只有详细信息能够帮助应用程序代码处理错误的时候,才应该引入其他的错误详细信息类型。如果错误信息只能由人来处理,请依赖于错误消息内容,并让开发人员手动处理它,而不是引入新的错误详细信息类型。请注意,如果引入了其他错误详细信息类型,则必须显式注册它们。 49 | 50 | 下面是一些示例`error_details`有效内容: 51 | 52 | * `RetryInfo`描述客户端何时可以重试失败的请求,可以返回`Code.UNAVAILABLE`或`Code.ABORTED` 53 | * `QuotaFailure`描述配额检查如何失败,可以返回`Code.RESOURCE_EXHAUSTED` 54 | * `BadRequest`描述客户端请求中的违例,可以返回`Code.INVALID_ARGUMENT` 55 | 56 | ## HTTP映射 57 | 58 | 虽然proto3消息支持原生JSON编码,Google API Platform使用以下JSON表示形式来处理直接HTTP-JSON错误响应,以保证向后兼容性: 59 | 60 | ``` 61 | { 62 | "error": { 63 | "code": 401, 64 | "message": "Request had invalid credentials.", 65 | "status": "UNAUTHENTICATED", 66 | "details": [{ 67 | "@type": "type.googleapis.com/google.rpc.RetryInfo", 68 | ... 69 | }] 70 | } 71 | } 72 | ``` 73 | 74 | 75 | 字段 | 描述 76 | ----------- | ----------------------------------------------------------------------- 77 | **error** | 用于向后兼容Google API客户端库的额外层。 它使用JSON来标示以便人类阅读。 78 | **code** | **Status.code**的HTTP状态代码映射 79 | **message** | 这对应于**Status.message** 80 | **status** | 这对应于**Status.status** 81 | **details** | 这对应于**Status.details** 82 | 83 | ## RPC 映射 84 | 85 | 不同的RPC协议以不同的方式映射错误模型。 对于[gRPC](http://www.grpc.io/),错误模型由生成的代码和每种语言的运行时库原生支持。 您可以在gRPC的API文档中找到更多信息(例如,请参阅gRPC Java的[`io.grpc.Status`](https://github.com/grpc/grpc-java/blob/master/core/src/main/java/io/grpc/Status.java))。 86 | 87 | ## 客户端库映射 88 | 89 | Google客户端库可能会选择以不同的语言显示错误,以保持与已建立的习语一致。 例如,[google-cloud-go](https://github.com/GoogleCloudPlatform/google-cloud-go)库将返回实现[`google.rpc.Status`](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto)相同接口的error,而[google-cloud-java](https://github.com/GoogleCloudPlatform/google-cloud-java)将抛出异常。 90 | 91 | ## 错误本地化 92 | 93 | [`google.rpc.Status`](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto)中的`message`字段面向开发人员,**必须**使用英语。 94 | 95 | 如果需要面向用户的错误消息,请使用[`google.rpc.LocalizedMessage`](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto)作为您的详细信息字段。 虽然[`google.rpc.LocalizedMessage`](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto)中的message字段可以本地化,但请确保[`google.rpc.Status`](https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto)中的消息字段为英语。 96 | 97 | 默认情况下,API服务应使用经过身份验证的用户语言环境或HTTP `Accept-Language`头来确定本地化的语言。 98 | 99 | ## 处理异常 100 | 101 | 下面是一个表格,其中包含[`google.rpc.Code`](https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto)中定义的所有gRPC错误代码及其原因的简短说明。 要处理错误,您可以检查返回状态码的描述,并相应地修改您的请求。 102 | 103 | HTTP | RPC | 描述 104 | ---- | ----------------------- | ------------------------------------------------------------------------------------------------------ 105 | 200 | **OK** | 没有错误 106 | 400 | **INVALID_ARGUMENT** | 客户端指定了无效的参数。 检查错误消息和错误详细信息以获取更多信息。 107 | 400 | **FAILED_PRECONDITION** | 请求不能在当前系统状态下执行,例如删除非空目录。 108 | 400 | **OUT_OF_RANGE** | 客户端指定了无效的范围。 109 | 401 | **UNAUTHENTICATED** | 由于遗失,无效或过期的OAuth令牌而导致请求未通过身份验证。 110 | 403 | **PERMISSION_DENIED** | 客户端没有足够的权限。这可能是因为OAuth令牌没有正确的范围,客户端没有权限,或者客户端项目尚未启用API。 111 | 404 | **NOT_FOUND** | 找不到指定的资源,或者该请求被未公开的原因(例如白名单)拒绝。 112 | 409 | **ABORTED** | 并发冲突,例如读-修改-写冲突。 113 | 409 | **ALREADY_EXISTS** | 客户端尝试创建的资源已存在。 114 | 429 | **RESOURCE_EXHAUSTED** | 资源配额达到速率限制。 客户端应该查找google.rpc.QuotaFailure错误详细信息以获取更多信息。 115 | 499 | **CANCELLED** | 客户端取消请求 116 | 500 | **DATA_LOSS** | 不可恢复的数据丢失或数据损坏。 客户端应该向用户报告错误。 117 | 500 | **UNKNOWN** | 未知的服务器错误。 通常是服务器错误。 118 | 500 | **INTERNAL** | 内部服务错误。 通常是服务器错误。 119 | 501 | **NOT_IMPLEMENTED** | 服务器未实现该API方法。 120 | 503 | **UNAVAILABLE** | 暂停服务。通常是服务器已经关闭。 121 | 504 | **DEADLINE_EXCEEDED** | 已超过请求期限。如果重复发生,请考虑降低请求的复杂性。 122 | 123 | ## 错误重试 124 | 125 | 客户端**应该**在遇到500,503和504错误的时候重试。 最小延迟应为1s,除非另有说明。 对于429错误,客户端可能会以最少30秒的延迟重试。对于所有其他错误,重试可能不适用-首先确保您的请求是幂等的,并检查错误消息指引。 126 | 127 | ## 错误传播 128 | 129 | 如果您的API服务依赖于其他服务,您不应盲目地将错误从服务端传播到您的客户端。翻译错误时,我们建议如下: 130 | - 隐藏接口实现的详细信息和机密信息。 131 | - 调整发生错误方。例如,从另一个服务接收`INVALID_ARGUMENT`错误的服务器应将`INTERNAL`传播给其自己的调用者。 132 | 133 | ## 生成错误 134 | 135 | 如果您是服务器开发人员,则应该使用足够的信息来生成错误,以帮助客户开发人员了解并解决问题。 同时,您必须了解用户数据的安全性和隐私性,并避免在错误消息和错误详细信息中暴露敏感信息(因为错误通常会被记录并可能被其他人访问)。 例如,“客户端IP地址不在白名单128.0.0.0/8”等错误消息公开了服务器端的策略信息,用户不应该访问该信息。 136 | 137 | 要生成正确的错误,您首先需要熟悉[`google.rpc.Code`](https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto),为每个错误条件选择最合适的错误代码。服务器应用程序可以并行地检查多个错误条件,并返回第一个错误条件。 138 | 139 | 下表列出了每个错误代码和一个良好错误消息的示例。 140 | 141 | HTTP | RPC | 消息实例 142 | ---- | ----------------------- | --------------------------------------------------------------- 143 | 400 | **INVALID_ARGUMENT** | Request field x.y.z is xxx, expected one of \[yyy, zzz\]. 144 | 400 | **FAILED_PRECONDITION** | Resource xxx is a non-empty directory, so it cannot be deleted. 145 | 400 | **OUT_OF_RANGE** | Parameter 'age' is out of range \[0, 125\]. 146 | 401 | **UNAUTHENTICATED** | Invalid authentication credentials. 147 | 403 | **PERMISSION_DENIED** | Permission 'xxx' denied on file 'yyy'. 148 | 404 | **NOT_FOUND** | Resource 'xxx' not found. 149 | 409 | **ABORTED** | Couldn’t acquire lock on resource ‘xxx’. 150 | 409 | **ALREADY_EXISTS** | Resource 'xxx' already exists. 151 | 429 | **RESOURCE_EXHAUSTED** | Quota limit 'xxx' exceeded. 152 | 499 | **CANCELLED** | Request cancelled by the client. 153 | 500 | **DATA_LOSS** | See note. 154 | 500 | **UNKNOWN** | See note. 155 | 500 | **INTERNAL** | See note. 156 | 501 | **NOT_IMPLEMENTED** | Method 'xxx' not implemented. 157 | 503 | **UNAVAILABLE** | See note. 158 | 504 | **DEADLINE_EXCEEDED** | See note. 159 | 160 | **注意**:由于客户端无法修复服务器错误,因此生成其他错误详细信息是无用的。 为了避免在错误条件下泄露敏感信息,建议不要生成除`google.rpc.DebugInfo`之外的错误详细信息。 `DebugInfo`是专门为服务器端日志记录设计的,**禁止**发送到客户端。 161 | 162 | `google.rpc`包定义了一组标准错误有效载荷,这是自定义错误有效载荷的首选。 下表列出了每个错误代码及其匹配的标准错误有效内容(如果适用)。 163 | 164 | HTTP | RPC | 推荐的错误细节 165 | ---- | ----------------------- | ---------------------------------- 166 | 400 | **INVALID_ARGUMENT** | **google.rpc.BadRequest** 167 | 400 | **FAILED_PRECONDITION** | **google.rpc.PreconditionFailure** 168 | 400 | **OUT_OF_RANGE** | **google.rpc.BadRequest** 169 | 401 | **UNAUTHENTICATED** | 170 | 403 | **PERMISSION_DENIED** | 171 | 404 | **NOT_FOUND** | 172 | 409 | **ABORTED** | 173 | 409 | **ALREADY_EXISTS** | 174 | 429 | **RESOURCE_EXHAUSTED** | **google.rpc.QuotaFailure** 175 | 499 | **CANCELLED** | 176 | 500 | **DATA_LOSS** | 177 | 500 | **UNKNOWN** | 178 | 500 | **INTERNAL** | 179 | 501 | **NOT_IMPLEMENTED** | 180 | 503 | **UNAVAILABLE** | 181 | 504 | **DEADLINE_EXCEEDED** | 182 | -------------------------------------------------------------------------------- /API-design-guide/08-命名约定.md: -------------------------------------------------------------------------------- 1 | # 命名约定 2 | 3 | 为了在跨API开发中向开发者提供一致的开发体验,所有的命名**应该**保证: 4 | 5 | * 简单 6 | * 直观 7 | * 一致 8 | 9 | 这适用于接口、资源、集合、方法以及消息的命名。 10 | 11 | 因为很多开发者并非以英语作为母语,所以命名约定的目标之一是确保大多数开发者可以更容易理解 API。对于方法和资源,我们鼓励使用简单、直观和一致的单词来命名。 12 | 13 | * API 中的命名**应该**使用正确的美式英语。例如,使用美式英语的 license 而非英式英语的 licence;使用美式英语的 color 而非英式英语的 colour 14 | * 为了简化起见,**可以**使用已被广泛接受的短语和(或)缩写。例如,使用 API 比Application Programming Interface更好 15 | * 尽量使用直观、熟悉的术语。例如,描述移除(removing)或销毁(destroying)一个资源时,使用删除(delete)比擦除(erase)更好 16 | * 为了避免歧义,对于同一种概念应当使用相同的名称或短语,这一点适用于跨越多个不同API的概念 17 | * 为了避免歧义,对于不同的概念应当使用不同的名称或短语 18 | * 为了避免在 API 的上下文以及更大的 Google API 生态系统中存在含糊不清和过于笼统的名称(它们可能导致对 API 概念的误解)。应当选择能准确描述 API 概念的名称,这对定义 API 中的资源名称特别重要。 19 | * 应当仔细思考那些**可能**与常用编程语言中关键字冲突的名称。 20 | 21 | 22 | 23 | ## 产品名称 24 | 25 | 26 | 产品名称应当参考那些API的公开产品名称,例如 Google Calendar API。同时,在API、用户界面、文档、服务协议、收费声明以及商业合同中,都**应该**使用一致的产品名称。 27 | 28 | Google 的 API **必须**使用以 *Google* 开头的产品名称,除非产品所属品牌不同。例如,Gmail、YouTube等。通常来说,产品名称**应该**由产品和市场团队来确定。 29 | 30 | 下面这个表格以我们的产品为例展示了命名的一致性。您也可以通过本页附录中的链接获取更多信息。 31 | 32 | | API 名称 | 示例 | 33 | | -------------- | -------------------------------------- | 34 | | **产品名称** | Google **Calendar** API | 35 | | **服务名称 ** | **calendar.googleapis.com** | 36 | | **包名称** | **google.calendar.v3** | 37 | | **接口名称** | **google.calendar.v3.CalendarService** | 38 | | **源代码目录** | **//google/calendar/v3** | 39 | | **API 名称** | **calendar** | 40 | 41 | 42 | ## 服务名称 43 | 44 | 服务名称**应当**符合 DNS 命名的语法规范(请参考 [RFC 1035 ](http://www.ietf.org/rfc/rfc1035.txt)规范),确保它可以被解析成一个或多个网络地址。公开的 Google API 的服务名称遵循这样的规则:```xxx.googleapis.com``` 。例如,Google Calendar 的服务名称是 ```calendar.googleapis.com``` 。 45 | 46 | 如果一个 API 是由多个服务组成,那么它们的命名**应当**更容易被发现。 一种方法是为所有服务名称共享一个公共前缀,例如,服务 ```build.googleapis.com``` 和 ```buildresults.googleapis.com``` 都是 Google Build API 的一部分。 47 | 48 | 49 | 50 | ## 包名称 51 | 52 | 在 API.proto 文件中声明包名称,它**应当**与产品名称和服务名称保持一致。同时 API 的包名称**必须**包含版本信息,例如: 53 | 54 | ``` 55 | // Google Calendar API 56 | package google.calendar.v3; 57 | ``` 58 | 59 | 抽象的 API 并不直接关联服务,例如 Google Watcher API,**应当**使用与产品名称一致的 proto 包名称: 60 | 61 | ``` 62 | // Google Watcher API 63 | package google.watcher.v1; 64 | ``` 65 | 66 | 在 API.proto 中定义的 Java 包名称**必须**与具有标准 Java 包名称前缀(```com.``` 、```edu.``` 、```net.``` 等)的 proto 包名称相匹配。例如: 67 | 68 | ``` 69 | package google.calendar.v3; 70 | 71 | // Specifies Java package name, using the standard prefix "com." 72 | option java_package = “com.google.calendar.v3"; 73 | ``` 74 | 75 | 76 | 77 | ## 集合标识符 78 | 79 | [集合命名](03-资源命名.md#资源组ID)**应该**使用复数形式、首字母小写驼峰体,并使用标准的美式英语。例如:```events```,```children``` 或 ```deletedEvents```。 80 | 81 | 82 | 83 | ## 接口名称 84 | 85 | 为了避免与[服务名称](08-命名约定#服务名称)混淆,比如 ```pubsub.google.apis.com``` ,这里的接口名称是指在 .proto 文件中定义服务(```service```)时使用的名称: 86 | 87 | ``` 88 | // Library is the interface name. 89 | service Library { 90 | rpc ListBooks(...) returns (...); 91 | rpc ... 92 | } 93 | ``` 94 | 95 | 你可以将服务名称看作是对一组 API 实现的引用,而接口名称则是 API 的抽象定义。 96 | 97 | 接口名称**应当**使用直观准确的名词,例如 Calendar 或 Blob。同时名称**不应当**与主流编程语言及其运行时库中的任何概念相冲突。 98 | 99 | 在极少的情况下,接口名称可能会与 API 中的其它名称冲突,这时**应当**使用后缀(```Api``` 或 ```Service```)来消除歧义。 100 | 101 | 102 | 103 | ## 方法名称 104 | 105 | 服务**可以**在其 IDL 规范中定义 RPC 方法,用来对应集合或资源上的方法。方法名称**应当**使用首字母小写驼峰体的动名词,并且通常这里的名词就是资源类型。 106 | 107 | | 动词 | 名词 | 方法名称 | 请求 | 响应 | 108 | | ---------- | -------- | -------------- | --------------------- | ------------------------- | 109 | | **List** | **Book** | **ListBooks** | **ListBooksRequest** | **ListBooksResponse** | 110 | | **Get** | **Book** | **GetBook** | **GetBookRequest** | **Book** | 111 | | **Create** | **Book** | **CreateBook** | **CreateBookRequest** | **Book** | 112 | | **Update** | **Book** | **UpdateBook** | **UpdateBookRequest** | **Book** | 113 | | **Rename** | **Book** | **RenameBook** | **RenameBookRequest** | **RenameBookResponse** | 114 | | **Delete** | **Book** | **DeleteBook** | **DeleteBookRequest** | **google.protobuf.Empty** | 115 | 116 | 117 | 118 | ## 消息名称 119 | 120 | RPC 方法的请求和响应消息**应该**分别以带有后缀 ```Request``` 和 ```Response``` 的方法名来命名,除非请求和响应的类型为: 121 | 122 | * 一个空消息(使用 ```google.protobuf.Empty```) 123 | * 一种资源 124 | * 一种表示操作的资源 125 | 126 | 这通常适用于使用标准 ```Get```、```Create```、```Update``` 或 ```Delete``` 方法的请求和响应。 127 | 128 | 129 | 130 | ## 枚举名称 131 | 132 | 枚举类型的名称**必须**使用首字母大写驼峰体。 133 | 134 | 枚举值**必须**使用“以下划线分隔的全大写”(CAPITALIZED_NAME_WITH_UNDERSCORES)方式命名,每个枚举值**必须**以分号(;)而非逗号(,)结尾。并且第一个枚举值**应该**以“枚举_类型_未指定”(ENUM_TYPE_UNSPECIFIED)的形式命名,该值用于枚举值没有显式指定时的默认值。 135 | 136 | ``` 137 | enum FooBar { 138 | // The first value represents the default and must be == 0. 139 | FOO_BAR_UNSPECIFIED = 0; 140 | FIRST_VALUE = 1; 141 | SECOND_VALUE = 2; 142 | } 143 | ``` 144 | 145 | 146 | 147 | ## 字段名称 148 | 149 | 在 .proto 文件中的定义字段时,**必须**使用“以下划线分隔的全小写方式”(lower_case_underscore_separated_names)命名。针对不同的编程语言,这些名称将根据命名规范/惯例被映射到自动生成代码中。 150 | 151 | 152 | 153 | ### 重复字段名称 154 | 155 | API 中的重复字段**必须**使用正确的复数形式。 这符合现有的 Google API 惯例,以及外部开发人员的普遍认知。 156 | 157 | 158 | 159 | ### 瞬时时间和持续时间 160 | 161 | 要表示一个与任何时区或日历无关的瞬时时间,**应当**使用 ```google.protobuf.Timestamp``` 类型,并且字段名称**应当**以时间```time```结束,例如 ```start_time``` 和 ```end_time```。 162 | 163 | 如果瞬态时间代表一个活动或行为,则字段名称**应当**采用“动词_时间”(```verb_time```)的形式,例如 ```create_time```,```update_time```。 同时注意避免使用动词的过去时态,例如 ```created_time``` 或 ```last_updated_time```。 164 | 165 | 要表示两个与任何日历和“天”/“月”无关的瞬时时间点之间的时间跨度,**应当**使用 google.protobuf.Duration 类型。 166 | 167 | ``` 168 | message FlightRecord { 169 | google.protobuf.Timestamp takeoff_time = 1; 170 | google.protobuf.Duration flight_duration = 2; 171 | } 172 | ``` 173 | 174 | 如果由于历史遗留或兼容性原因,不得不使用整数类型表示与时间相关的字段,那么字段名称**必须**采用以下格式: 175 | 176 | ``` 177 | xxx_{time|duration|delay|latency}_{seconds|millis|micros|nanos} 178 | ``` 179 | 180 | ``` 181 | message Email { 182 | int64 send_time_millis = 1; 183 | int64 receive_time_millis = 2; 184 | } 185 | ``` 186 | 187 | 如果由于历史遗留或兼容性原因,不得不使用字符串类型表示时间戳,那么字段名称**不应包**含任何时间单位后缀,同时字符串**应该**使用 RFC 3339 格式。例如,“2014-07-30T10:43:17Z”。 188 | 189 | 190 | 191 | ### 日期和时刻 192 | 193 | 对于与任何时区和时刻无关的日期,**应当**使用 ```google.type.Date``` 类型,并使用“\_日期”(```_date```)后缀。如果日期必须表示为字符串类型,则应该使用 ISO 8601日期格式 YYYY-MM-DD。例如,2014-07-30。 194 | 195 | 对于与任何时区和日期无关的时段,**应当**使用 ```google.type.TimeOfDay``` 类型,并使用“\_时间”(```_time```)后缀。 如果时刻必须表示为字符串类型,则应该使用 ISO 8601 24时格式 HH:MM:SS [.FFF]。例如,14:55:01.672。 196 | 197 | ``` 198 | message StoreOpening { 199 | google.type.Date opening_date = 1; 200 | google.type.TimeOfDay opening_time = 2; 201 | } 202 | ``` 203 | 204 | 205 | 206 | ### 数量 207 | 208 | 由整数类型表示的数量**必须**包含计量单位。 209 | 210 | ``` 211 | xxx_{bytes|width_pixels|meters} 212 | ``` 213 | 214 | 如果该数量代表多个内容的计数,则字段名城**应该**包含“\_计数” (```_count```)后缀。例如,```node_count```。 215 | 216 | 217 | 218 | ### 列表过滤器字段 219 | 220 | 如果 API 支持对 ```List``` 方法返回的资源进行过滤,那么包含过滤器表达式的字段**应该**命名为 ```filter```。 例如: 221 | 222 | ``` 223 | message ListBooksRequest { 224 | // The parent resource name. 225 | string parent = 1; 226 | 227 | // The filter expression. 228 | string filter = 2; 229 | } 230 | ``` 231 | 232 | 233 | 234 | ### 列表响应 235 | 236 | ```List``` 方法的响应消息中包含资源列表的字段名称**必须**是资源名称本身的复数形式。例如,```CalendarApi.ListEvents()``` 方法**必须**为返回的资源列表定义一个响应消息 ```ListEventsResponse```,其中包含一个称为 ```events``` 的重复字段。 237 | 238 | ``` 239 | service CalendarApi { 240 | rpc ListEvents(ListEventsRequest) returns (ListEventsResponse) { 241 | option (google.api.http) = { 242 | get: "/v3/{parent=calendars/*}/events"; 243 | }; 244 | } 245 | } 246 | 247 | message ListEventsRequest { 248 | string parent = 1; 249 | int32 page_size = 2; 250 | string page_token = 3; 251 | } 252 | 253 | message ListEventsResponse { 254 | repeated Event events = 1; 255 | string next_page_token = 2; 256 | } 257 | ``` 258 | 259 | 260 | 261 | ## 驼峰体 262 | 263 | 除了字段名称和枚举值,```.proto``` 文件中的所有名称都**必须**使用 [Google Java Style](https://google.github.io/styleguide/javaguide.html#s5.3-camel-case) 定义的首字母大写驼峰体(UpperCamelCase)。 264 | 265 | 266 | 267 | ## 名称缩写 268 | 269 | 对于软件开发人员熟知的名称缩写,例如 ```config``` 和 ```spec```,建议在 API 定义中**应该**使用缩写,而不是完整的名称。 这将使源代码易于读写。而在正式文档中,**应当**使用完整拼写的名称。例如: 270 | 271 | * 配置(config/configuration) 272 | * 标识符(id/identifier) 273 | * 规格(spec/specification) 274 | * 统计(stats/statistics) 275 | -------------------------------------------------------------------------------- /API-design-guide/09-一般设计模式.md: -------------------------------------------------------------------------------- 1 | # 一般设计模式 2 | 3 | 4 | 5 | ## 空白响应 6 | 7 | 标准的 ```Delete``` 方法必须返回 ```google.protobuf.Empty``` 才能获得全局一致性。它还可以防止客户端重试时所依赖的元数据不可用。对于自定义方法,它们必须有自己的形如 ```XxxResponse``` 的消息,即使它们是空白的,因为很可能在方法功能变化后,需要返回额外的数据。 8 | 9 | 10 | 11 | ## 表示范围 12 | 13 | 用来表达范围的字段应当使用半开区间的命名约定,形如:```[start_xxx,end_xxx)```,例如 ```[start_key,end_key]``` 或 ```[start_time,end_time)```。 C++ STL 库和 Java 标准库通常使用这种半开区间语义。API 应避免使用其他表示范围的方法,例如 ```(index,count)``` 或 ```[first,last]```。 14 | 15 | 16 | 17 | ## 资源标签 18 | 19 | 在面向资源的 API 中,资源模式由 API 来定义。为了让客户端将少量的简单元数据附加到资源(例如,将一个虚拟机标记为数据库服务器),API **应该**使用 ```google.api.LabelDescriptor``` 中描述的资源标签设计模式。 20 | 21 | 为此,设计 API 时**应该**向资源定义中添加字段 ```map``` 标签。 22 | 23 | ``` 24 | message Book { 25 | string name = 1; 26 | map labels = 2; 27 | } 28 | ``` 29 | 30 | 31 | 32 | ## 长时运行的操作 33 | 34 | 如果一个 API 方法通常需要很长时间才能执行完成,那么它可以设计成向客户端返回一个长时间运行(Long Running Operation)资源,客户端可以使用该资源来跟踪进度并获取结果。该[操作](https://github.com/googleapis/googleapis/blob/master/google/longrunning/operations.proto)定义了一个标准接口,用于长时间运行的操作。各个 API **禁止**为长时间运行的操作定义自己的接口,以避免不一致。 35 | 36 | 操作资源**必须**作为响应消息直接返回,并且操作的任何直接后果都**应该**反映在API中。例如,在创建资源时,尽管资源**应该**指示它没有准备好被使用,该资源也**应该**出现在LIST和GET方法中。操作完成后,如果方法不长时间运行,`Operation.response`字段应包含直接返回的消息。 37 | 38 | 39 | 40 | ## 列表分页 41 | 42 | 以列表形式获取的集合**应该**支持分页,即使结果集通常很小。 43 | 44 | **原理**:即使向现有的 API 添加分页支持仅仅是从 API 表面视角添加,那也是一个会破坏 API 行为的更改。现有的客户端由于不知道分页,将错误地假设会接收到完整的列表结果,而实际客户端只接收到第一页。 45 | 46 | 为了使 ```List``` 方法支持分页(在页面中返回列表结果),API **应该**: 47 | 48 | * 在 ```List``` 方法的请求消息中定义一个 ```string``` 类型字段 ```page_token```。客户端使用此字段请求列表结果的特定页面。 49 | * 在 ```List``` 方法的请求消息中定义一个 ```int32``` 类型字段 ```page_size```。客户端使用此字段指定服务器一次返回的最大结果数。服务器仍然**可以**进一步限制在单个页面中返回的结果的最大数量,如果 ```page_size``` 为0,服务器将决定要返回的结果数。 50 | * 在 ```List``` 方法的响应消息中定义一个 ```string``` 字段 ```next_page_token```。此字段代表用于检索下一页的分页令牌。如果值为空字符串,则表示没有更多结果了。 51 | 52 | 要检索下一页结果,客户端**应该**在后续 ```List``` 方法调用(在请求消息的 ```page_token``` 字段中)传递前一次响应的 ```next_page_token``` 的值: 53 | 54 | ``` 55 | rpc ListBooks(ListBooksRequest) returns (ListBooksResponse); 56 | 57 | message ListBooksRequest { 58 | string name = 1; 59 | int32 page_size = 2; 60 | string page_token = 3; 61 | } 62 | 63 | message ListBooksResponse { 64 | repeated Book books = 1; 65 | string next_page_token = 2; 66 | } 67 | ``` 68 | 69 | 当客户端传递除页面令牌之外的查询参数时,如果查询参数与页面令牌不一致,则服务**必须**使请求失败。 70 | 71 | 页面令牌的内容**应该**是一个 base64 编码过的PB(protocol buffer)。 这避免了兼容性问题,如果页面令牌包含潜在的敏感信息,那么该信息**应该**被加密。 同时服务**必须**防止通过以下方法篡改页面令牌以泄露数据: 72 | 73 | * 在后续请求时必须重新指定查询参数。 74 | * 在页面令牌中只引用服务器端的会话状态。 75 | * 页面令牌中加密并签名的的查询参数,需要在每次调用时都重新验证和重新授权。 76 | 77 | 分页的具体实现中也**可以**提供名为 ```total_size``` 的 ```int32``` 字段来表示总数。 78 | 79 | 80 | 81 | ## 列出子集合 82 | 83 | 有时,API 需要让客户端在子集合之间执行`List/Search`方法。例如,图书馆 API 有一组书架,每个书架都有一组书籍,而客户想要在所有书架上搜索一本书。在这种情况下,建议对子集合使用标准`List`方法,并为父集合指定通配符集合标识`“-”`。对于 Library API 示例,我们可以使用以下 REST API 请求: 84 | 85 | ```GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx``` 86 | 87 | 注意:使用`“-”`而不是`“*”`的原因是为了避免需要进行URL转义。 88 | 89 | 90 | 91 | ## 从子集合获取唯一资源 92 | 93 | 有时,子集合中的一个资源具有在其父集合内唯一的标识符。在这种情况下,允许通过 ```Get``` 来检索它并且不需要知道其属于哪个父集合。同时,建议使用标准 ```Get```,并为所有父集合指定通配符集合标识`“-”`。例如,在图书馆 API 中,如果图书在所有书架上的所有图书中是唯一的,则可以使用以下 REST API 请求: 94 | 95 | ```GET https://library.googleapis.com/v1/shelves/-/books/{id}``` 96 | 97 | 上面这个请求的响应中的资源**必须**使用资源的规范名称,对于每个父集合,应使用实际父集合标识符而不是`“-”`。例如,上面的请求应该返回资源名为 ```shelves/shelf713/books/book8141```,而不是 ```shelves/-/books/book8141```。 98 | 99 | 100 | 101 | ## 排序顺序 102 | 103 | 如果 API 方法允许客户端指定列表结果的排序顺序,请求消息**应该**包含一个字段: 104 | 105 | ```string order_by = ...;``` 106 | 107 | 排序字符串值**应该**遵循 SQL 语法:逗号分隔字段列表。例如:```“foo,bar”```,默认排序顺序为升序,要为字段指定降序,**应该**在字段名后附加后缀 ```“desc”```。例如:```“foo desc,bar”```。 108 | 109 | 语法中的冗余空格字符无关紧要。```“foo,bar desc”``` 和 ```“ foo ,bar desc ”``` 是等效的。 110 | 111 | 112 | 113 | ## 请求验证 114 | 115 | 如果 API 方法有副作用,并且需要验证请求而避免此类副作用,请求消息**应该**包含一个字段: 116 | 117 | ```bool validate_only = ...;``` 118 | 119 | 如果此字段设置为 ```true```,则服务器*禁止*执行任何副作用逻辑,只能执行特定于实现的与完整请求一致的验证逻辑。 120 | 121 | 如果验证成功,则**必须**返回 ```google.rpc.Code.OK```,任何使用相同请求消息的完整请求都**不应**返回 ```google.rpc.Code.INVALID_ARGUMENT```。注意,请求可能由于其他错误(如 ```google.rpc.Code.ALREADY_EXISTS```)或由于竞争而失败。 122 | 123 | 124 | 125 | ## 请求重复 126 | 127 | 对于网络 API,幂等 API 方法是更优的选择,因为它们可以在网络故障后安全地重试。然而,一些 API 方法无法容易地实现成幂等的。例如创建资源,并且需要避免不必要的重复。对于这种使用情况,请求消息**应该**包含唯一的 ID,例如 UUID,服务器将使用该唯一 ID 来检测复制,并确保请求仅被处理一次。 128 | 129 | ``` 130 | // A unique request ID for server to detect duplicated requests. 131 | // This field **should** be named as `request_id`. 132 | string request_id = ...; 133 | ``` 134 | 135 | 如果检测到重复请求,则服务器**应该**返回先前成功请求的响应,因为客户端很可能未收到先前的响应。 136 | 137 | 138 | 139 | ## 枚举默认值 140 | 141 | 每个枚举定义**必须**以 ```0``` 值条目开始,当未明确指定枚举值时,**将**使用该条目。API **必须**在文档中记录如何处理 ```0``` 值。 142 | 143 | 如果存在默认行为,则**应该**使用枚举值 ```0```,并且 API 应在文档中记录预期行为。 144 | 145 | 如果没有默认行为,则枚举值 ```0``` **应该**命名为 ```ENUM_TYPE_UNSPECIFIED```,并且**应该**以错误 ```INVALID_ARGUMENT``` 来拒绝使用。 146 | 147 | ``` 148 | enum Isolation { 149 | // Not specified. 150 | ISOLATION_UNSPECIFIED = 0; 151 | // Reads from a snapshot. Collisions occur if all reads and writes cannot be 152 | // logically serialized with concurrent transactions. 153 | SERIALIZABLE = 1; 154 | // Reads from a snapshot. Collisions occur if concurrent transactions write 155 | // to the same rows. 156 | SNAPSHOT = 2; 157 | ... 158 | } 159 | 160 | // When unspecified, the server will use an isolation level of SNAPSHOT or 161 | // better. 162 | Isolation level = 1; 163 | ``` 164 | 165 | **可以**为 ```0```值使用一个惯用名称。例如,```google.rpc.Code.OK``` 是指定缺少错误代码的惯用方法。 在这种情况下,```OK``` 在语义上等同于枚举类型定义中的 ```UNSPECIFIED```。 166 | 167 | 在存在内在敏感和安全默认值的情况下,该值**可以**用于“0”值。 例如,`BASIC`是[资源视图](#资源视图)枚举中的“0”值。 168 | 169 | ## 语法句法 170 | 171 | 在某些 API 设计中,有必要为某些数据格式定义简单的语法,例如可接受的文本输入。为了在 API 之间提供一致的开发体验并降低学习曲线,API 设计者**必须**使用 [ISO 14977](http://www.iso.org/iso/catalogue_detail?csnumber=26153) 扩展的巴斯克-诺尔格式(EBNF)句法来定义语法: 172 | 173 | ``` 174 | Production = name "=" [ Expression ] ";" ; 175 | Expression = Alternative { "|" Alternative } ; 176 | Alternative = Term { Term } ; 177 | Term = name | TOKEN | Group | Option | Repetition ; 178 | Group = "(" Expression ")" ; 179 | Option = "[" Expression "]" ; 180 | Repetition = "{" Expression "}" ; 181 | ``` 182 | 183 | **注意**:```TOKEN``` 代表在语法之外定义的终止符。 184 | 185 | 186 | 187 | ## 整数类型 188 | 189 | 在 API 设计中,**不应**使用无符号整数类型,例如 ```uint32``` 和 ```fixed32```,因为一些重要的编程语言不能很好地支持它们,例如 Java 和 JavaScript,他们可能导致溢出错误。另一个问题是,不同的 API 非常喜欢在同一个场景中混用不匹配的有符号和无符号整数类型。 190 | 191 | 当有符号整数类型用于负值无意义的场景(例如尺寸或超时)时,值 -1(且**只有** -1)可能用于指代特殊的含义,例如文件结尾(EOF)、无限超时、无限配额以及未知的年龄。这些用法**必须**清楚地记录在文档中,以避免混淆。API 创建者还应该记录隐式默认值 0 的行为。 192 | 193 | 194 | 195 | ## 部分响应 196 | 197 | 有时,一个 API 客户端只需要响应消息中的特定数据子集。为了支持这样的场景,一些 API 平台提供对部分响应的原生支持。Google API 平台通过响应消息中的字段掩码支持这一特性。对于任何 REST API 调用,都有一个隐式系统查询参数 ```$fields```,它是 ```google.protobuf.FieldMask``` 值的 JSON 表示形式。响应消息将在发送回客户端之前由 ```$field``` 过滤。 API 平台为所有的 API 方法自动处理此逻辑。 198 | 199 | ```GET https://library.googleapis.com/v1/shelves?$fields=name``` 200 | 201 | 202 | 203 | ## 资源视图 204 | 205 | 为了减少网络流量,有时允许客户端限制服务器在其响应中返回的资源的部分,返回资源的视图而不是完整的资源表示形式,这一做法是非常有用的。 API 中的资源视图通过向方法请求添加参数来实现该功能,该参数允许客户端指定需要在响应中接收的资源。 206 | 207 | 参数: 208 | 209 | * **应该**是 ```enum``` 类型 210 | * **必须**命名为 ```view``` 211 | 212 | 每个枚举值定义了资源的哪些部分(哪些字段)将在服务器的响应中返回。每个视图(```view```)值应该返回什么样的资源视图是由实现来定义的,同时**应该**在 API 文档中记录。 213 | 214 | ``` 215 | package google.example.library.v1; 216 | 217 | service Library { 218 | rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) { 219 | option (google.api.http) = { 220 | get: "/v1/{name=shelves/*}/books" 221 | } 222 | }; 223 | } 224 | 225 | enum BookView { 226 | // Server responses only include author, title, ISBN and unique book ID. 227 | // The default value. 228 | BASIC = 0; 229 | 230 | // Full representation of the book is returned in server responses, 231 | // including contents of the book. 232 | FULL = 1; 233 | } 234 | 235 | message ListBooksRequest { 236 | string name = 1; 237 | 238 | // Specifies which parts of the book resource should be returned 239 | // in the response. 240 | BookView view = 2; 241 | } 242 | ``` 243 | 244 | 这个结构将映射成类似下面的 URL 地址: 245 | 246 | ``` 247 | GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC 248 | ``` 249 | 250 | 您可以在本设计指南的[标准方法](04-标准方法.md)一章中找到有关定义方法、请求和响应的更多信息。 251 | 252 | 253 | 254 | ## ETags 255 | 256 | ETag 是一个非透明的标识符,允许客户端进行带条件的请求。为了支持 ETag,API **应该**在资源定义中包含一个 ```string``` 字段 ```etag```,其语义**必须**与ETag的通用用法相匹配。通常,etag包含由服务器计算出的资源的指纹。有关更多详细信息,请参阅 [Wikipedia](https://en.wikipedia.org/wiki/HTTP_ETag) 和 [RFC 7232](https://tools.ietf.org/html/rfc7232#section-2.3) 规范。 257 | 258 | ETag 可以被强验证或弱验证,其中弱验证的 ETag 前缀为 ```W/```。在此上下文中,强验证意味着具有相同 ETag 的两个资源具有逐字节完全相同的内容和相同的额外字段(例如,Content-Type)。这意味着经过强验证的 ETag 允许缓存部分响应并可以稍后用于组合。 259 | 260 | 相反,具有相同弱验证的 ETag 值的资源意味着在语义上等同,但不一定是逐字节相同的,因此不适合于缓存响应。 261 | 262 | 例如: 263 | 264 | ``` 265 | // This is a strong ETag, including the quotes. 266 | "1a2f3e4d5b6c7c" 267 | // This is a weak ETag, including the prefix and quotes. 268 | W/"1a2b3c4d5ef" 269 | ``` 270 | 271 | 272 | 273 | ## 输出字段 274 | 275 | API 可能需要区分由客户端作为输入提供的字段,以及只由服务器返回的特定资源的输出字段。对于仅为输出的字段,字段属性**应该**在文档中记录。 276 | 277 | 注意,如果客户端在请求中对”仅输出字段“设值了,或者客户端指定了仅包含输出字段的 ```google.protobuf.FieldMask```,则服务器**必须**接受请求,而不会出错。 这意味着服务器**必须**忽略那些”仅输出字段“。因为客户端通常会重用由服务器返回的资源,并将其作为另一个请求输入。例如,检索到的 `Book` 资源将在稍后用作 UPDATE 方法的请求。如果服务器验证“仅输出字段“,则客户端需要额外工作以清除这些“仅输出字段”。 278 | 279 | ``` 280 | message Book { 281 | string name = 1; 282 | // Output only. 283 | Timestamp create_time = 2; 284 | } 285 | ``` 286 | 287 | -------------------------------------------------------------------------------- /API-design-guide/10-文档.md: -------------------------------------------------------------------------------- 1 | # 文档 2 | 3 | 本章节为 API 添加文档提供了指南。 大多数 API 将提供概述、教程和更高级别的参考文档,这些内容本设计指南并不涉及。 有关 API 命名、资源命名和方法命名的信息,请参阅[命名约定](08-命名约定.md)。 4 | 5 | ## 注释格式 6 | 7 | 在 `.proto` 文件中可以使用 Protocol Buffers 通常的注释格式(`\\`)来添加注释 8 | 9 | ``` 10 | // Creates a shelf in the library, and returns the new Shelf. 11 | rpc CreateShelf(CreateShelfRequest) returns (Shelf) { 12 | option (google.api.http) = { post: "/v1/shelves" body: "shelf" }; 13 | } 14 | ``` 15 | 16 | ## 服务配置中的注释 17 | 18 | 另一种向 `.proto` 文件添加注释的方法是,可以在其 YAML 服务配置文件中为 API 添加内联文档。 如果两个文件中都记录了相同的元素,则 YAML 中的文档将优先于 `.proto` 中的文档。 19 | 20 | ```yaml 21 | documentation: 22 | summary: Gets and lists social activities 23 | overview: A simple example service that lets you get and list possible social activities 24 | rules: 25 | - selector: google.social.Social.GetActivity 26 | description: Gets a social activity. If the activity does not exist, returns Code.NOT_FOUND. 27 | ``` 28 | 29 | 如果有多个服务定义在同一个 `.proto` 文件中,并且希望为每个服务提供文档,则可能需要使用此方法。在 YAML 文还可以为 API 添加更详细的`概述`。但是一般情况下,推荐在 `.proto` 文件中添加文档注释。 30 | 31 | 与 `.proto` 注释一样,可以使用 Markdown 为 YAML 中的注释提供更多排版格式。 32 | 33 | 34 | 35 | ## API 描述 36 | 37 | API 描述是以行为动词开头的短语,描述了该 API 可以执行什么操作。在 `.proto` 文件中,API 描述作为注释添加到相应的服务(`service`)上,例如: 38 | 39 | ``` 40 | // Manages books and shelves in a simple digital library. 41 | service LibraryService { 42 | ... 43 | } 44 | ``` 45 | 46 | 下面是一些 API 描述的示例: 47 | 48 | * 分享你的动态、照片、视频给其他朋友。 49 | * 访问云托管的机器学习服务,可以轻松构建响应数据流的智能应用程序。 50 | 51 | 52 | 53 | ## 资源描述 54 | 55 | 资源描述是描述资源代表了什么的句子。如果需要添加更多详细信息,使用添加其他句子。在 `.proto` 文件中,资源描述作为注释添加到对应的消息类型上,如下: 56 | 57 | ``` 58 | // A book resource in the Library API. 59 | message Book { 60 | ... 61 | } 62 | ``` 63 | 64 | 下面是一些资源描述的示例: 65 | 66 | * 代表用户待办事项中的一项任务。每项任务具有唯一的优先级。 67 | * 代表用户日历上的一个事件。 68 | 69 | 70 | 71 | ## 字段和参数描述 72 | 73 | 一个描述字段或参数定义的名词短语,例如: 74 | 75 | * 话题数量。 76 | * 经纬度坐标的精度(以米为单位),必须为非负数。 77 | * 标记是否为提交资源返回附件地址,`series.insert` 的默认值为 `true`。 78 | * 用于投票信息的容器,仅当记录投票信息时才存在。 79 | * 目前未使用或已弃用。 80 | 81 | 字段和参数描述 82 | 83 | * 必须清楚地描述边界(也就是说,明确什么是有效的什么是无效的。请记住,工程师不会阅读底下的代码来明确任何不清楚的信息。) 84 | * 必须指定默认值或默认行为。换句话说,如果没有提供任何值服务器将会是什么行为。 85 | * 如果是字符串(如名称或路径),请描述其语法规则并列出允许的字符以及所需的编码。例如: 86 | * 集合 [A-a0-9] 中1~255个字符 87 | * 以 / 开头的有效 URL 路径字符串,必须遵循 RFC 2332 约定。最大允许长度为500个字符 88 | * 尽可能提供示例值。 89 | * 如果字段值是**必需的、或仅用于输入、或仅用于输出**,那么必须在字段描述的开始处注明。默认情况下,所有字段和参数都是可选的。例如: 90 | 91 | ``` 92 | proto message Table { 93 | // Required. The resource name of the table. 94 | string name = 1; 95 | // Input only. Whether to dry run the table creation. 96 | bool dyrun = 2; 97 | // Output only. The timestamp when the table was created. Assigned by 98 | // the server. 99 | Timestamp create_time = 3; 100 | // The display name of the table. 101 | string display_name = 4; 102 | } 103 | ``` 104 | 105 | 106 | 107 | ## 方法描述 108 | 109 | 方法描述是一个句子,指出该方法具有什么效果以及操作的资源。它通常以第三人称现在时态[动词](https://developers.google.com/internal/style/reference-verbs)(即以 s 结尾的动词)开始。如果需要添加详细信息,可以使用更多的句子。例如: 110 | 111 | * 列出已认证用户的日历事件。 112 | * 使用请求中包含的数据来更新日历事件。 113 | * 从已认证用户的位置历史记录中删除一个位置记录。 114 | * 在已认证用户的位置历史记录中,使用请求中包含的数据创建或更新一个位置记录。如果已存在具有相同时间戳的位置记录,则用提供的数据覆盖现有数据。 115 | 116 | 117 | 118 | ## 描述信息备忘录 119 | 120 | 确保每个描述都是简短但完整的,并且能够让用户在没有 API 其他信息的情况下所理解。实际上,还有更多需要注意的事项。例如,方法 `series.insert` 的描述不应该只是说“插入一个系列”。尽管你在命名时应该易于理解,但读者之所以阅读你的描述,是因为他们需要获取更多的信息。如果您不确定需要在描述中表述什么,可以参考下面这些问题: 121 | 122 | * 它是什么? 123 | * 如果(请求)成功它会做什么?如果失败它会做什么?什么可能导致失败? 124 | * 它是幂等的吗? 125 | * 单位是什么? (例如:米,度,像素。) 126 | * 它接受什么范围的值?范围是包含还是排他? 127 | * 有什么副作用吗? 128 | * 你该如何使用它? 129 | * 可能破坏它的常见错误有哪些? 130 | * 它总是存在吗? (例如:“用于投票信息的容器,仅在记录投票信息时存在”。) 131 | * 它有默认设置吗? 132 | 133 | 134 | 135 | ## 约定 136 | 137 | 本节列出了文本描述和文档的一些使用约定。例如,在谈论标识符时,使用 “ID”(全大写),而不是 “Id” 或 “id”。在指代 JSON 数据格式时,请使用 “JSON” 而不是 “Json” 或 “json”。使用```代码字体```来表示所有字段和参数名称。字符串字面值以```代码字体```表示并放入引号中。 138 | 139 | * ID 140 | * JSON 141 | * RPC 142 | * REST 143 | * ```property_name``` 或 ```“string_literal”``` 144 | * ```true```/```false``` 145 | 146 | 147 | 148 | #### 语言风格 149 | 150 | 正如上一章节的[命名约定](08-命名约定.md),我们建议在编写文档注释时使用简单,一致的词汇。 注释应该易于母语非英语的读者理解,所以避免行话、俚语、复杂的隐喻、引用流行文化,或任何其他不容易翻译的内容。阅读注释时好似以友好、专业的风格直接与开发人员交流。请谨记,读者是为了了解如何使用 API,而不是阅读你的文档! -------------------------------------------------------------------------------- /API-design-guide/11-使用Proto3.md: -------------------------------------------------------------------------------- 1 | # Protocol Buffers v3 2 | 3 | 本章将讨论在设计 API 时如何使用 Protocol Buffers。为了简化开发者体验并提高运行效率,gRPC API **应该**使用 Protocol Buffers v3(proto3)进行API定义。 4 | 5 | [Protocol Buffers](https://github.com/google/protobuf) 是一种简单的语言中立并且平台中立的接口定义语言(IDL),用于定义数据结构模式和编程接口。它支持二进制和文本传输格式,并且在不同平台上使用不同的传输协议。 6 | 7 | Proto3 是 Protocol Buffers 的最新版本,包括以下更改: 8 | 9 | * 存在字段,也称为 ```hasField```,不适用于基本类型字段。未设置的基本泪ing字段具有语言特定的默认值。 10 | * 消息存在字段仍然可用,其可以使用编译器生成的 ```hasField``` 方法来测试,或者和 null 比较,或由实现定义的标记值。 11 | * 用户定义的默认值不再可用。 12 | * 枚举(Enum)定义**必须**从零开始。 13 | * 必填(Required fields)字段不再可用。 14 | * 扩展(Extensions)不再可用。改用 ```google.protobuf.Any``` 代替。 15 | * 由于向后和运行时兼容性原因,```google/protobuf/descriptor.proto``` 作为这一规则的例外。 16 | * 组(Group)语法已删除。 17 | 18 | 删除这些功能的原因是使 API 设计更简单,更稳定,更高效。例如,通常需要在记录消息之前过滤一些字段,例如删除敏感信息,如果字段是必需的,则不可能执行删除操作。 19 | 20 | 有关详细信息,请参阅协议[Protocol Buffers详细文档](https://developers.google.com/protocol-buffers/)。 21 | 22 | 23 | -------------------------------------------------------------------------------- /API-design-guide/12-版本控制.md: -------------------------------------------------------------------------------- 1 | # 版本控制 2 | 3 | 本章提供了网络 API 的版本控制指南。由于一个 API 服务**可能**提供多个 [API 接口](17-术语表.md#API接口\(API Interface\)),因此 [API 版本](17-术语表.md#API版本)控制策略适用于API 接口级别,而不适用于 [API 服务](17-术语表.md#API服务\(API Service))级别。 为了方便起见,术语 API 指的是以下各节中的 API 接口。 4 | 5 | 网络API**应该**使用[语义化的版本](http://semver.org/)。比如给定版本号 ```MAJOR.MINOR.PATCH```: 6 | 7 | 1. 当做出不兼容修改的时候,修改 ```MAJOR``` 版本号 8 | 2. 当以向后兼容的方式添加功能时,修改 ```MINOR``` 版本号 9 | 3. 当进行向后兼容的错误修复时,修改 ```PATCH``` 版本号 10 | 11 | 根据 API 版本,指定*主要版本号*(major version number)时可使用不同的规则: 12 | 13 | * 对于 API 第一版(v1),其主要版本号**应该**编码进 proto 包名称中,例如 ```google.pubsub.v1```。如果包中不含有将来会变化的类型和接口,例如 ```google.protobuf``` 和 ```google.longrunning```,则**可以**从 proto 包中省略主版本号。 14 | * 对于v1之外的所有版本的 API,主版本号必须编码进原包名中。例如,```google.pubsub.v2```。 15 | 16 | 对于 GA 之前的版本,例如 alpha 和 beta,建议在版本号后面附加一个后缀,**应该**包括预发布版本名称(例如alpha,beta)和预发布版本号。 17 | 18 | 版本升级示例: 19 | 20 | | 版本 | 描述 | 21 | | --------- | ------------------------------------------------------- | 22 | | v1alpha | alpha 版本 | 23 | | v1beta1 | beta 版本。因为破坏兼容性可以接受,所以不需要更新版本号 | 24 | | v1beta1 | 可信测试版 | 25 | | v1beta2 | 可信测试版本迭代 | 26 | | v1test | 带有假数据的可信测试版 | 27 | | v1 | 正式发布版 | 28 | | v1.1beta1 | v1小改版的beta版 | 29 | | v1.1 | v1小改版 | 30 | | v2beta1 | 新大版本的beta版 | 31 | | v2 | 新大版本,正式发布版 | 32 | 33 | minor 和 patch 版本号**应该**反映在 API 配置和文档中,它们**不允许**被编码在原始程序包名称中。 34 | 35 | 注意:One Platform目前不支持 minor 和 patch 版本。 API 所有者需要通过 API 文档和发行说明记录它们。 36 | 37 | API 的新 major 版本**不能**依赖于以前**同样API**的主要版本。 API 在了解相关的依赖性和稳定性风险的情况下**可以**依赖其他 API。 API **必须**只依赖于其他API的最新稳定版本。 38 | 39 | 在一段时间内,同一 API 的不同版本**必须**能够在客户端应用程序中同时工作。这是为了帮助客户端从旧版本平滑过渡到更新版本的 API。 40 | 41 | 旧的 API 版本**应该**在其弃用期结束后删除。 42 | 43 | 由许多 API 共享的公用而稳定的数据类型,例如日期和时间,**应该**在单独的 proto 包中定义。如果需要进行中断更改,则**必须**引入新类型名称或具有新的主要版本的包名称。 44 | 45 | ## 向后兼容 46 | 47 | 确定向后兼容的修改是困难的。 48 | 49 | 以下列表是一份快速参考,如果您有任何疑问,请参阅[兼容性](13-兼容性.md)部分以了解更多详细信息。 50 | 51 | ### 向后兼容的修改 52 | 53 | * 将 API 接口添加到 API 服务定义 54 | * 向 API 接口添加方法 55 | * 向方法添加 HTTP 绑定 56 | * 向请求消息中添加字段 57 | * 向响应消息中添加字段 58 | * 向枚举添加值 59 | * 添加仅输出的资源字段 60 | 61 | ### 向后不兼容的修改 62 | 63 | * 删除或重命名服务,字段,方法或枚举值 64 | * 更改 HTTP 绑定 65 | * 更改字段的类型 66 | * 更改资源命名格式 67 | * 更改现有请求的可见行为 68 | * 更改 HTTP 定义中的 URL 格式 69 | * 向资源消息添加读/写字段 70 | 71 | 72 | -------------------------------------------------------------------------------- /API-design-guide/13-兼容性.md: -------------------------------------------------------------------------------- 1 | # 兼容性 2 | 3 | 本章提供了有关 [版本控制](12-版本控制.md) 章节中提供的破坏性和非破坏性修改列表的详细说明。 4 | 5 | 什么算是一个破坏性(不兼容)的变化并没有明确的定义。本指南**应该**被视为指示性的,而不是每一种可能变化的全面清单。 6 | 7 | 这里列出的规则只涉及客户端兼容性。预期API生产者明白在部署方面的要求,包括实现细节的变化。 8 | 9 | 一般目的是,服务端更新到一个新的minor版本或patch版本不该破坏客户端。可预期的破坏类型有: 10 | 11 | * 源代码兼容性:针对1.0编写的代码无法在1.1版本编译 12 | * 二进制兼容性:编译的1.0版本的库在1.1客户端库情况下无法链接/运行。(具体细节取决于客户端平台;在不同情况下有不同表现)。 13 | * 传输兼容性:针对1.0构建的应用程序无法与1.1服务器通信 14 | * 语义兼容性:可以运行,但产生无意义的或令人惊讶的结果 15 | 16 | 换句话说:旧客户端应该能够兼容同一major版本号的较新的服务器,并且当后者试图更新到新的minor版本(例如利用新功能)时,可以很容易做到。 17 | 18 | 除了理论上的基于协议的考虑之外,也有出于客户端库生成代码和手写代码的实际考虑。尽可能思考在生成新版本的客户端库过程中会产生哪些变化,针对它们写测试用例,并确保其通过测试。 19 | 20 | 下面的讨论将原始消息分为三类: 21 | 22 | * 请求消息(例如 `GetBookRequest`) 23 | * 响应消息(例如 `ListBooksResponse`) 24 | * 资源消息(例如 `Book`,以及包括在其他资源消息中使用的任何消息) 25 | 26 | 这些类别具有不同的规则,因为请求消息只能从客户端发送到服务器,响应消息只能从服务器发送到客户端,但通常资源消息是双向发送。特别强调,对可更新的资源,需要考虑其 读取/修改/写入 循环的顺序。 27 | 28 | ## 向后兼容(非破坏性)的修改 29 | 30 | ### 给API服务定义添加API接口 31 | 32 | 从协议的角度来看,这始终是安全的。 唯一需要注意的是,客户端库可能已经在手写的代码中使用了您的新API接口名。 如果新接口与现有接口完全正交,那就没问题(不可能正交); 如果新接口是现有接口的简化版本,则更有可能导致冲突。 33 | 34 | ### 给API接口添加方法 35 | 36 | 除非你添加的方法和客户端库已经生成的方法冲突,否则应该没问题。 37 | 38 | (举一个破坏性的例子:如果你有一个 `GetFoo` 方法,C#代码生成器已经创建了`GetFoo`和`GetFooAsync`方法.在API接口中添加一个`GetFooAsync`方法,从客户端角度,这就是一个破坏性的修改。) 39 | 40 | ### 给方法添加HTTP绑定 41 | 42 | 假设绑定不引入任何歧义,使得服务器响应以前会拒绝的URL,这就是安全的。 当将现有操作应用于新资源名称模式时,**可以**这样做。 43 | 44 | ### 给请求消息添加字段 45 | 46 | 只要客户端在新版和旧版中对该字段的处理不保持一致,添加请求字段就是兼容的。 47 | 48 | 最显而易见的反面例子是分页:如果v1.0的API在集合中不包括分页,则不能在v1.1中添加分页,除非新版中的`page_size`默认为无限大(这通常是一个坏主意)。 否则,希望在单个请求中获取完整结果的v1.0客户端可能会收到不完整的结果,并且不知道结果集包含更多资源。 49 | 50 | ### 给响应消息添加字段 51 | 52 | 在不改变其他响应字段的行为的前提下,非资源(例如,`ListBooksResponse`)的响应消息可以扩展而不必破坏客户端的兼容性。即使会引入冗余,先前在响应中填充的任何字段应继续使用相同的语义填充。 53 | 54 | 例如,1.0中的查询响应可能包含一个布尔字段`contained_duplicates`,用来标示某些结果因为重复而被忽略。 在1.1中,我们可能会在`duplicate_count`字段中提供更详细的信息。 即使从1.1版本的角度来看它是冗余的,仍然**必须**填充`contained_duplicates`字段。 55 | 56 | ### 给枚举(enum)添加值 57 | 58 | 仅在请求消息中使用的枚举可以自由地扩展新值。例如,使用 [资源视图](09-通用设计模式.md#资源视图) 模式,可以在新的minor版本中添加新视图。客户端从来不需要接收这个枚举,所以它们不必知道它们不关心的值。 59 | 60 | 对于资源消息和响应消息,缺省假设客户端应该处理他们不知道的枚举值。然而,API生产者应该意识到,编写能正确处理新枚举值的应用程序可能很困难。 API所有者**应该**在文档里描述当遇到未知枚举值时,预期的客户端行为。 61 | 62 | Proto3允许客户端接收不明意义并保留相同值的消息,因此不会中断 读取/修改/写入 周期。 63 | JSON格式允发送一个名称是未知的值,但是服务器端显然不知道客户端是否能正确理解该字段的确切值。JSON客户端可能意识到已经接收了一个未知的值,要么识别出字符串"name",要么是数字,不可能两者都识别。客户端会在 读取/修改/写入 周期中原封不动的将值返回服务器,因为服务器应该理解这两种格式。 64 | 65 | 66 | ### 给仅输出的资源添加字段 67 | 68 | 仅由服务器提供的资源实体中**可以**添加字段。 服务器**可以**验证请求中的任何客户端提供的值,但是如果省略该值,服务器**禁止**失败。 69 | 70 | ## 向后不兼容(破坏性)的修改 71 | 72 | ### 删除或重命名服务,字段,方法或枚举值 73 | 74 | 从根本上说,如果客户端代码可以引用某些东西,那么删除或重命名它都是不兼容的变化,这时**必须**修改major版本号。 引用旧名称的代码将导致某些语言(例如C#和Java)在编译期出现故障,并可能导致其他语言的执行时失败或丢失数据。 传输格式兼容性与此无关。 75 | 76 | ### 修改HTTP绑定 77 | 78 | “修改”在这里特指“删除和添加”。 例如,如果您确定要支持PATCH方法,但是您发布的版本支持PUT方法,或者您使用了错误的自定义方法名称,你**可以**添加新的绑定,但**禁止**删除旧绑定,原因同删除一个服务方法一样,都是一个不兼容的变化。 79 | 80 | ### 修改字段的类型 81 | 82 | 即使新类型是传输格式兼容的,这也可能会导致客户端库生成的代码发生变化,因此**必须**增加major版本号。 对于编译型静态语言来说,会容易引入编译错误。 83 | 84 | ### 修改资源命名格式 85 | 86 | 资源**禁止**修改其名称 - 这意味着集合名称不能修改。 87 | 88 | 与大多数不兼容修改不同,这也影响major版本号:如果预期客户端使用v2.0 API访问在v1.0 API中创建的资源(反之亦然),则应在两个版本中使用相同的资源名称。 89 | 90 | 更细微地讲,有效资源名称的集合也**不应该**修改,原因如下: 91 | 92 | * 如果变得更加严格,则以前成功的请求现在将失败。 93 | 94 | * 如果它变得更宽松,那么基于前面文档做出的客户端可能会不兼容。客户端很可能在其他地方存储资源名称,可能对允许的字符集和名称的长度敏感。或者,客户端可能正在执行自己的资源名称验证。 (例如,在允许更长的EC2资源ID时,[亚马逊给客户发出了很多警告,并且提供了一个迁移期](https://aws.amazon.com/cn/blogs/aws/theyre-here-longer-ec2-resource-ids-now-available/)) 95 | 96 | 注意,这样的改变只能在proto文档中可见。因此,当审查CL的兼容性时,仅检查非注释部分修改是不够的。 97 | 98 | ### 修改现有请求的可见行为 99 | 100 | 客户端通常依赖于API行为和语义,**即使这样的行为没有被明确支持或记录**。 因此,在大多数情况下,修改API数据的行为或语义将被消费者视为是破坏性的。 如果行为没有加密隐藏,您**应该**假设用户已经发现它,并将依赖于它。 例如,[用户对AWS EC2资源标识符进行了逆向](http://www.jackofallclouds.com/2009/09/anatomy-of-an-amazon-ec2-resource-id/)。 101 | 102 | 因此,加密分页令牌(即使数据没有啥意义)是一个好主意,以防止用户创建自己的令牌,有潜在的令牌行为改变时可能会不兼容的风险。 103 | 104 | ### 修改HTTP定义中的URL格式 105 | 106 | 除了上面列出的资源名称修改外,有两种修改需要考虑: 107 | 108 | * 自定义方法名称:虽然不是资源名称的一部分,但自定义方法名称是提交到REST客户端的URL的一部分。 修改自定义方法名称不应破坏gRPC客户端,但公共API必须假定它们具有REST客户端。 109 | 110 | * 资源参数名称:从`v1/shelves/{shelf}/books/{book}`修改为`v1/shelves/{shelf_id}/books/{book_id}`不会影响替代资源名称,但可能会影响代码生成。 111 | 112 | ### 给资源消息添加 读取/写入 字段 113 | 114 | 客户端将经常执行 读取/修改/写入 操作。 大多数客户端不会为他们不知道的字段提供值,而且proto3也不支持。 您可以指定任何缺少消息类型(而不是原始类型)的字段,这意味着更新不适用于这些字段,也使得从实体中显式移除该字段值变得更加困难。 原始类型(包括字符串和字节)不能这样处理,因为在显式指定int32字段为0和没有指定之间,proto3的表现没有什么不同。 115 | 116 | 如果所有更新都是使用字段掩码执行,这就不是问题,因为客户端不会隐式地覆盖其不知道的字段。 然而,这将是一个不寻常的API决策:大多数API允许“整个资源”更新。 117 | 118 | 119 | -------------------------------------------------------------------------------- /API-design-guide/14-目录结构.md: -------------------------------------------------------------------------------- 1 | # 目录结构 2 | 3 | API服务通常使用`.proto`文件来定义API接口,并通过`.yaml`文件来配置API服务。 4 | 5 | 每个API服务都**必须**在API仓库中有一个API目录,其中包含其定义文件和构建脚本。 6 | 7 | API目录**应该**具有以下标准布局: 8 | 9 | * API目录 10 | 11 | * 配置文件 12 | 13 | * `{service}.yaml` - 主服务配置文件,它是`google.api.Service` proto消息的YAML表示形式。 14 | 15 | * `prod.yaml` - 生产环境增量服务配置文件。 16 | 17 | * `staging.yaml` - 模拟环境增量服务配置文件。 18 | 19 | * `test.yaml` - 测试环境增量服务配置文件。 20 | 21 | * `local.yaml` - 本地环境增量服务配置文件。 22 | 23 | ``` 24 | * 接口定义 25 | 26 | - `v[0-9]*/*` - 每个这样的目录包含一个major版本API,主要是proto文件和构建脚本。 27 | - `{subapi}/v[0-9]*/*` - 每一个`{subapi}`目录包含一个子API的接口定义。 28 | 每一个子API可以拥有它自己的独立主版本。 29 | - `type/*` - 在不同的API之间,或者同一个API的不同版本之间,或API和服务实现之间, 30 | 共享的类型的proto文件。 在`type/*`下的类型定义一旦发布之后**不应该**有不兼容的修改。 31 | ``` -------------------------------------------------------------------------------- /API-design-guide/15-文件结构.md: -------------------------------------------------------------------------------- 1 | # 文件结构 2 | 3 | gRPC APIs **应该** 在后缀是.proto的文件中用[proto3](https://cloud.google.com/apis/design/proto3) 交互式数据语言定义。 4 | 5 | 文件结构 **必须** 坚持较高等级和更重要的定义在前,较低等级和重要性较低的定义在后的原则。在每一个proto文件中,可以接受的章节顺序如下所示: 6 | 7 | - 版权和许可声明(如果需要的话) 8 | - Proto `syntax`,`package`,`option`和`import`的声明(注意顺序) 9 | - API 概述,方便读者快速了解文章的剩余内容 10 | - API proto `服务`定义,按照重要顺序从高到低 11 | - 资源`消息`体定义,父级**必须**在其子级的前面定义 12 | - RPC请求和响应的`消息`定义,保持相关方法的先后顺序。每个请求消息**必须**在相应的响应消息(如果有的话)前面定义 13 | 14 | 如果一个单一的proto文件包含了所有的API定义,**应该**以API名称作为文件名,例如: 15 | 16 | | API | Proto | 17 | | ------------ | ------------------ | 18 | | **Library** | **library.proto** | 19 | | **Calendar** | **calendar.proto** | 20 | 21 | 大型的.proto文件可以分割成多个小文件。可以将服务、资源消息和请求/响应消息,分别保存在不同的文件中。 22 | 23 | 如果一个proto文件里既有服务,又有相关的请求/响应,那么我们建议该文件名是 `<包含服务名的名称>.proto`; 24 | 如果一个proto文件里面只有资源,那么可以考虑简单的命名该文件为`resources.proto`. 25 | 26 | ## Proto 文件名 27 | Proto 文件名 **应该** 小写,下划线分隔,并且 **必须** 使用 `·proto` 作为后缀名。 例如: `service_controller.proto`。 28 | 29 | ## Proto 选项 30 | 31 | 为了保证跨不同API的客户端库的一致性,API开发者 **必须** 在.proto文件中使用一致的proto选项(option)。符合本文规范的API定义 **必须** 使用如下的文件级别的proto选项: 32 | 33 | ``` 34 | syntax = "proto3"; 35 | 36 | // package名称应该是公司名开头,主版本号结尾 37 | package google.abc.xyz.v1; 38 | 39 | // 本选项描述了C#语言中使用的命名空间。 40 | // proto包的名称默认是驼峰式命名规则,由多个单一单词构成的段组成,每个段之间用'.'分隔。 41 | // 例如,一个包名称是"google.shopping.pets.v1",就会使用C#的命名空间 "Google.Shopping.Pets.V1". 42 | // 然而,如果构成包名称的任意一个段是由多个单词构成的,要避免只有第一个单词的首字母大写。 43 | // 例如,假设一个谷歌宠物商店API的包名称是"google.shopping.petstore.v1", 44 | // 对应的C#风格的命名空间就是"Google.Shopping.Petstore.V1". 45 | // 但是, 正确的大写规则应该是"Google.Shopping.PetStore.V1" 46 | // 更多的C#/.NET大写规则细节,请参考[Framework Design Guidelines](https://msdn.microsoft.com/en-us/library/ms229043) 47 | option csharp_namespace = "Google.Abc.Xyz.V1"; 48 | 49 | // 本选项让proto编译器在包名(参考下一选项)内部产生Java代码,而不是在一个外部类以内。 50 | // 通过减少一层的命名嵌套,提供了一种更简单的开发体验,这与其他大多数不支持外部类的编程语言保持一致. 51 | option java_multiple_files = true; 52 | 53 | // Java外部类的名称应该符合驼峰命名法则. 该类只是用来持有proto的描述符,所以开发者不需要直接与它打交道。 54 | option java_outer_classname = "XyzProto"; 55 | 56 | // Java包名必须是proto包名加上适当的前缀 57 | option java_package = "com.google.abc.xyz.v1"; 58 | 59 | // 给包提供一个能够合理标识Objective-C的前缀。 60 | // 至少3个字母,全部大写,一般都是包名称的缩写。 61 | // 短小,但是具有一定独特性,预期不会在将来产生冲突。 62 | // 'GPB'是谷歌protocol buffer实现的保留字。 63 | option objc_class_prefix = "GABCX"; 64 | ``` 65 | -------------------------------------------------------------------------------- /API-design-guide/16-HTTP 方法.md: -------------------------------------------------------------------------------- 1 | # HTTP 方法 2 | 3 | 网络化API支持多种传输协议,包括HTTP。因此,每一个API方法 **必须** 遵守其映射到的特定HTTP方法的HTTP协议相关的要求。更多细节,请参考超[文本传输协议规范](https://tools.ietf.org/html/rfc2616#section-9)和它的[补丁方法](http://tools.ietf.org/html/rfc5789)RFC。 4 | 5 | 安全的方法,比如 HTTP GET(和HEAD),除了检索以外不应该执行任何动作。 特别地, HTTP GET 应该被认为是安全的,而且不应该有任何客户端可见的副作用。 6 | 7 | HTTP的幂等意味着,多个相同的请求和一个请求的副作用相同。GET, PUT, 和 DELETE 都是幂等的HTTP方法。注意,幂等只针对服务器端的副作用,与任何响应无关。特别要注意的是,DELETE 不存在的资源 **应该** 返回404 (Not Found)。 8 | 9 | 10 | HTTP POST 和 PATCH 方法,既不是安全的,也不是幂等的。 (PATCH 请参考[RFC 5789](https://tools.ietf.org/html/rfc5789)) 11 | 12 | | HTTP Verb | Safe | Idempotent | 13 | | --------------------------------------------------------- | ---- | ---------- | 14 | | [GET](https://tools.ietf.org/html/rfc2616#section-9.3) | Yes | Yes | 15 | | [PUT](https://tools.ietf.org/html/rfc2616#section-9.6) | | Yes | 16 | | [DELETE](https://tools.ietf.org/html/rfc2616#section-9.7) | | Yes | 17 | | [POST](https://tools.ietf.org/html/rfc2616#section-9.5) | | Yes | 18 | | [PATCH](https://tools.ietf.org/html/rfc5789) | | Yes | 19 | -------------------------------------------------------------------------------- /API-design-guide/17-术语表.md: -------------------------------------------------------------------------------- 1 | # 术语表 # 2 | 3 | ### 网络化API(Networked APIs) ### 4 | - 在计算机网络上运转的面向应用编程的API。API之间通过包括HTTP在内的各种网络协议进行通讯,不同的机构定义出大量的API,通常都是供大于求. 5 | 6 | ### 谷歌API(Google APIs) ### 7 | 8 | - 由谷歌提供的网络化API。大部分都托管在`googleapis.com`. 不包含其他类型的API,比如客户端的第三方库和SDK. 9 | 10 | ### API接口(API Interface) ### 11 | 12 | - 一个Protocol Buffers的服务定义,通常是映射到多种编程语言的一个具体接口。 一个API接口应该可以被任意数量的API服务调用。 13 | 14 | ### API版本 ### 15 | 16 | - API接口、或一组API接口(如果它们一起定义)的版本。 API版本通常由字符串(例如“v1”)表示,并呈现在API请求和协议缓冲区的包名中。 17 | 18 | ### API方法(API Method) ### 19 | 20 | - 在一个API接口中的一个独立行为,通常是映射到多种编程语言的API接口中的一个函数。通过一个`rpc`定义的Protocol Buffers呈现。 21 | 22 | ### API请求(API Request) ### 23 | 24 | - 一个单独的API方法的调用.常见的应用场景有:计费、日志、监控和频率限制。 25 | 26 | ### API服务(API Service) ### 27 | 28 | - 一个部署在一个或多个网络终端上的,实现了一个或多个API接口的服务。 一个API服务的名称应该具有一定的辨识度(符合[RFC 1035 DNS规范](https://www.ietf.org/rfc/rfc1035.txt)),比如calendar.googleapis.com。 29 | 30 | ### API终端(API Endpoint) ### 31 | 32 | - 一个API服务用来响应真实的API请求的网络地址,比如`pubsub.googleapis.com` 和 `content-pubsub.googleapis.com`。 33 | 34 | ### API产品(API Product) ### 35 | 36 | - 一个API服务,再加上服务条款,文档,客户端库,服务支持等相关内容,就构成了API产品,统一的呈现给客户。比如,谷歌日历(Google Calendar API) 注: 人们有时候简单的以一个API来代指一个API产品。 37 | 38 | ### API服务定义(API Service Definition) ### 39 | 40 | - 用来定义一个API服务的API接口定义(.proto文件)和API服务配置(.yaml文件),统称为API服务定义(API Service Definition)。 41 | 42 | ### API消费者(API Consumer) ### 43 | 44 | - 使用API服务的实体。以谷歌API(Google APIs)为例,通常是一个拥有客户端应用或者服务器资源的谷歌项目。 45 | 46 | ### API生产者(API Producer) ### 47 | 48 | - 生产API服务的实体。以谷歌API(Google APIs)为例,通常是一个拥有API服务的谷歌项目。 49 | 50 | ### API后端(API Backend) ### 51 | 52 | - 实现了一个API服务的商业逻辑的一组服务加上相关的基础设施。 53 | 54 | ### API前端(API Frontend) ### 55 | 56 | - 跨多个API服务的,提供通用功能的,一组服务加上相关的基础设施。比如负载均衡和验证。注:API前端和API后端在运行时,有可能很近,也可能很远。某些情况下,它们可能被打包编译成一个单独的二进制文件,在一个单进程里面运行。 57 | -------------------------------------------------------------------------------- /API-design-guide/README.md: -------------------------------------------------------------------------------- 1 | Google API Design Guide 中文版 2 | 3 | [https://cloud.google.com/apis/design/](https://cloud.google.com/apis/design/) 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitbook 2 | 高可用架构集体创作文档 3 | --------------------------------------------------------------------------------