Google Cloud API 设计指南

API 设计指南

简介

这是联网 API 的通用设计指南。它自 2014 年起在 Google 内部使用,是 Google 在设计 Cloud API 和其他 Google API 时遵循的指南。我们在此公开此设计指南,目的是为外部开发者提供信息,使我们所有人都能更轻松地协同工作。

Cloud Endpoints 开发者可能会发现本指南在设计 gRPC API 时特别有用,我们强烈建议此类开发者遵循这些设计原则。不过,我们并不强制要求使用本指南。不按照本指南,您也可以使用 Cloud Endpoints 和 gRPC。

本指南同时适用于 REST API 和 RPC API,尤其适用于 gRPC API。gRPC API 使用 Protocol Buffers 定义其 API 表面 (surface) 和 API 服务配置,以配置其 API 服务,包括 HTTP 映射、日志记录和监控。 Google API 和 Cloud Endpoints gRPC API 使用 HTTP 映射功能进行 JSON/HTTP 到 Protocol Buffers/RPC 的转码

本指南是一份活文档,随着时间的推移,我们会采纳和批准新的风格和设计模式,为本指南增加相关内容。本着这种精神,我们会不断完善本指南,并为 API 设计的艺术和技巧提供充足的空间。

本文档中使用的惯例

本文档中使用的要求级别关键字(“必须”、“不得”、“必需”,“应”、“不应”、“应该”、“不应该”、“建议”、“可以”和“可选”)应按 RFC 2119 中的描述进行解释。

在本文档中,这些关键字使用粗体突出显示。

基于资源的设计

本设计指南的目标是帮助开发者设计简单、一致且易用的网络 API。同时,它还有助于将 RPC API(基于套接字)与 REST API(基于 HTTP)的设计融合起来。

RPC API 通常根据接口和方法设计。随着时间的推移,接口和方法越来越多,最终结果可能是形成一个庞大而混乱的 API 接口,因为开发者必须单独学习每种方法。显然,这既耗时又容易出错。

引入 REST 架构风格主要是为了与 HTTP/1.1 配合使用,但也有助于解决这个问题。其核心原则是定义可以用少量方法控制的命名资源。这些资源和方法被称为 API 的“名词”和“动词”。使用 HTTP 协议时,资源名称自然映射到网址,方法自然映射到 HTTP 的 POST、GET、PUT、PATCH 和 DELETE。这使得要学习的内容减少了很多,因为开发人员可以专注于资源及其关系,并假定它们拥有的标准方法同样很少。

近来,HTTP REST API 在互联网上取得了巨大成功。2010 年,大约 74% 的公共网络 API 是 HTTP REST API。

虽然 HTTP REST API 在互联网上非常流行,但它们承载的流量比传统的 RPC API 要小。例如,美国高峰时段大约一半的互联网流量是视频内容,显然出于性能考虑,很少有人会使用 REST API 来传送此类内容。在数据中心内,许多公司使用基于套接字的 RPC API 来承载大多数网络流量,这可能涉及比公共 REST API 高几个数量级的数据(以字节为单位)。

在实际使用中,人们会出于不同目的选择 RPC API 和 HTTP REST API,理想情况下,API 平台应该为所有类型的 API 提供最佳支持。本设计指南可帮助您设计和构建符合此原则的 API。它将面向资源的设计原则应用于通用 API 设计并定义了许多常见的设计模式,从而提高可用性并降低复杂性。

注意:本设计指南介绍了如何将 REST 原则应用于 API 设计,与编程语言、操作系统或网络协议无关。这不仅仅是一个创建 REST API 的指南。

什么是 REST API?

REST API 是可单独寻址的“资源”(API 中的“名词”)的“集合”。资源通过资源名称被引用,并通过一组“方法”(也称为“动词”或“操作”)进行控制。

REST Google API 的标准方法(也称为“REST 方法”)包括 ListGetCreateUpdateDelete。API 设计者还可以使用“自定义方法”(也称为“自定义动词”或“自定义操作”)来实现无法轻易映射到标准方法的功能(例如数据库事务)。

注意:自定义动词并不意味着创建自定义 HTTP 动词来支持自定义方法。对基于 HTTP 的 API 而言,它们只是映射到最合适的 HTTP 动词。

设计流程

设计指南建议在设计面向资源的 API 时采取以下步骤(更多细节将在下面的特定部分中介绍):

  • 确定 API 提供的资源类型。
  • 确定资源之间的关系。
  • 根据类型和关系确定资源名称方案。
  • 确定资源架构。
  • 将最小的方法集附加到资源。

资源

面向资源的 API 通常被构建为资源层次结构,其中每个节点是一个“简单资源”或“集合资源”。 为方便起见,它们通常被分别称为资源和集合。

  • 一个集合包含相同类型的资源列表。 例如,一个用户拥有一组联系人。
  • 资源具有一些状态和零个或多个子资源。 每个子资源可以是一个简单资源或一个集合资源。

例如,Gmail API 有一组用户,每个用户都有一组消息、一组线程、一组标签、一个个人资料资源和若干设置资源。

虽然存储系统和 REST API 之间存在一些概念上的对应,但具有面向资源 API 的服务不一定是数据库,并且在解释资源和方法方面具有极大的灵活性。例如,创建日历事件(资源)可以为参与者创建附加事件、向参与者发送电子邮件邀请、预约会议室以及更新视频会议时间安排。

方法

面向资源的 API 的关键特性是,强调资源(数据模型)甚于资源上执行的方法(功能)。典型的面向资源的 API 使用少量方法公开大量资源。方法可以是标准方法或自定义方法。对于本指南,标准方法有:ListGetCreateUpdateDelete

如果 API 功能能够自然映射到标准方法,则应该在 API 设计中使用该方法。对于不会自然映射到某一标准方法的功能,可以使用自定义方法。自定义方法提供与传统 RPC API 相同的设计自由度,可用于实现常见的编程模式,例如数据库事务或数据分析。

示例

以下部分介绍了如何将面向资源的 API 设计应用于大规模服务的一些实际示例。您可以在 Google API 代码库中找到更多示例。

在这些示例中,星号表示列表中的一个特定资源。

Gmail API

Gmail API 服务实现了 Gmail API 并公开了大多数 Gmail 功能。它具有以下资源模型:

  • API 服务:gmail.googleapis.com
    • 用户集合:users/* 每个用户都拥有以下资源
      • 消息集合:users/*/messages/*
      • 线程集合:users/*/threads/*
      • 标签集合:users/*/labels/*
      • 变更历史记录集合:users/*/history/*
      • 表示用户个人资料的资源:users/*/profile
      • 表示用户设置的资源:users/*/settings

Cloud Pub/Sub API

pubsub.googleapis.com 服务实现了 Cloud Pub/Sub AP,后者定义以下资源模型:

  • API 服务:pubsub.googleapis.com
    • 主题集合:projects/*/topics/*
    • 订阅集合:projects/*/subscriptions/*

注意:Pub/Sub API 的其他实现可以选择不同的资源命名方案。

Cloud Spanner API

spanner.googleapis.com 服务实现了 Cloud Spanner API,后者定义了以下资源模型:

  • API 服务:spanner.googleapis.com
    • 实例集合:projects/*/instances/*
      • 实例操作的集合:projects/*/instances/*/operations/*
      • 数据库的集合:projects/*/instances/*/databases/*
      • 数据库操作的集合:projects/*/instances/*/databases/*/operations/*
      • 数据库会话的集合:projects/*/instances/*/databases/*/sessions/*

资源名称

在面向资源的 API 中,“资源”是被命名的实体,“资源名称”是它们的标识符。每个资源都必须具有自己唯一的资源名称。 资源名称由资源自身的 ID、任何父资源的 ID 及其 API 服务名称组成。在下文中,我们将查看资源 ID 以及如何构建资源名称。

gRPC API 应使用无传输协议的 URI 作为资源名称。它们通常遵循 REST 网址规则,其行为与网络文件路径非常相似。它们可以轻松映射到 REST 网址:如需了解详情,请参阅标准方法部分。

“集合”是一种特殊的资源,包含相同类型的子资源列表。例如,目录是文件资源的集合。集合的资源 ID 称为集合 ID。

资源名称由集合 ID 和资源 ID 构成,按分层方式组织并以正斜杠分隔。如果资源包含子资源,则子资源的名称由父资源名称后跟子资源的 ID 组成,也以正斜杠分隔。

示例 1:存储服务具有一组 buckets,其中每个存储分区都有一组 objects

API服务名称集合ID资源ID集合ID资源ID
//storage.googleapis.com/buckets/bucket-id/objects/object-id

示例 2:电子邮件服务具有一组 users。每个用户都有一个 settings 子资源,而 settings 子资源拥有包括 customFrom 在内的许多其他子资源:

API服务名称集合ID资源ID集合ID资源ID
//mail.googleapis.com/users/name@example.com/settings/customFrom

API 生产者可以为资源和集合 ID 选择任何可接受的值,只要它们在资源层次结构中是唯一的。您可以在下文中找到有关选择适当的资源和集合 ID 的更多准则。

通过拆分资源名称(例如 name.split("/")[n]),可以获得单个集合 ID 和资源 ID(假设任何段都不包含正斜杠)。

完整资源名称

无传输协议的 URIDNS 兼容的 API 服务名称和资源路径组成。资源路径也称为“相对资源名称”。 例如:

1
"//library.googleapis.com/shelves/shelf1/books/book2"

API 服务名称供客户端定位 API 服务端点;它可以是仅限内部服务的虚构 DNS 名称。如果 API 服务名称在上下文中很明显,则通常使用相对资源名称。

相对资源名称

开头没有“/”的 URI 路径 (path-noscheme)。它标识 API 服务中的资源。例如:

1
"shelves/shelf1/books/book2"

资源 ID

标识其父资源中资源的非空 URI 段 (segment-nz-nc),请参见上文的示例。

资源名称末尾的资源 ID 可以具有多个 URI 段。例如:

集合 ID资源 ID
files/source/py/parser.py

API 服务应该尽可能使用网址友好的资源 ID。 资源 ID 必须被清楚地记录,无论它们是由客户端、服务器还是其中一个分配的。例如,文件名通常由客户端分配,而电子邮件消息 ID 通常由服务器分配。

集合 ID

标识其父资源中集合资源的非空 URI 段 (segment-nz-nc),请参见上文的示例。

由于集合 ID 通常出现在生成的客户端库中,因此它们必须符合以下要求:

  • 必须是有效的 C/C++ 标识符。
  • 必须是复数形式的首字母小写驼峰体。如果该词语没有合适的复数形式,例如“evidence(证据)”和“weather(天气)”,则应该使用单数形式。
  • 必须使用简明扼要的英文词语。
  • 应该避免过于笼统的词语,或对其进行限定后再使用。例如,rowValues优先于values。应该避免在不加以限定的情况下使用以下词语:
    • elements
    • entries
    • instances
    • items
    • objects
    • resources
    • types
    • values

资源名称和网址

虽然完整的资源名称类似于普通网址,但两者并不相同。单个资源可以由不同的 API 版本、API 协议或 API 网络端点公开。完整资源名称未指明此类信息,因此在实际使用中必须将其映射到特定的 API 版本和 API 协议。

要通过 REST API 使用完整资源名称,必须将其转换为 REST 网址,实现方法为在服务名称之前添加 HTTPS 传输协议、在资源路径之前添加 API 主要版本以及对资源路径进行网址转义。例如:

1
2
3
4
5
// This is a calendar event resource name.
"//calendar.googleapis.com/users/john smith/events/123"
// This is the corresponding HTTP URL.
"https://calendar.googleapis.com/v3/users/john%20smith/events/123"

资源名称为字符串

除非存在向后兼容问题,否则 Google API 必须使用纯字符串来表示资源名称。资源名称应该像普通文件路径一样处理,并且它们不支持 % 编码。

对于资源定义,第一个字段应该是资源名称的字符串字段,并且应该称为 name

注意:以下代码示例使用 gRPC 转码语法。请点击链接以查看详细信息。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
service LibraryService {
rpc GetBook(GetBookRequest) returns (Book) {
option (google.api.http) = {
get: "/v1/{name=shelves/*/books/*}"
};
};
rpc CreateBook(CreateBookRequest) returns (Book) {
option (google.api.http) = {
post: "/v1/{parent=shelves/*}/books"
body: "book"
};
};
}
message Book {
// Resource name of the book. It must have the format of "shelves/*/books/*".
// For example: "shelves/shelf1/books/book2".
string name = 1;
// ... other properties
}
message GetBookRequest {
// Resource name of a book. For example: "shelves/shelf1/books/book2".
string name = 1;
}
message CreateBookRequest {
// Resource name of the parent resource where to create the book.
// For example: "shelves/shelf1".
string parent = 1;
// The Book resource to be created. Client must not set the `Book.name` field.
Book book = 2;
}

注意:为了保证资源名称的一致性,前导正斜杠不得被任何网址模板变量捕获。例如,必须使用网址模板 "/v1/{name=shelves/*/books/*}",而非 "/v1{name=/shelves/*/books/*}"

问题

问:为什么不使用资源 ID 来标识资源?

答:任何大型系统都有很多种资源。在使用资源 ID 来标识资源的时候,我们实际上是使用特定于资源的元组来标识资源,例如 (bucket, object)(user, album, photo)。这会带来几个主要问题:

  • 开发者必须了解并记住这些匿名元组。
  • 传递元组通常比传递字符串更难。
  • 集中式基础架构(例如日志记录和访问控制系统)不理解专用元组。
  • 专用元组限制了 API 设计的灵活性,例如提供可重复使用的 API 接口。例如,长时间运行的操作可以与许多其他 API 接口一起使用,因为它们使用灵活的资源名称。

问:为什么特殊字段名为 name 而不是 id

答:特殊字段以资源“名称”的概念命名。一般来说,我们发现 name 的概念让开发者感到困惑。例如,文件名实际上只是名称还是完整路径?通过预留标准字段 name,开发者不得不选择更合适的词语,例如 display_nametitlefull_name

标准方法

本章定义了标准方法(即 ListGetCreateUpdateDelete 的概念。标准方法可降低复杂性并提高一致性。Google API 代码库中超过 70% 的 API 方法都是标准方法,这使得它们更易于学习和使用。

下表描述了如何将标准方法映射到 HTTP 方法:

标准方法HTTP映射HTTP请求正文HTTP响应正文
ListGET资源*列表
GetGET资源*
CreatePOST资源资源*
UpdatePUT or PATCH资源资源*
DeleteDELETEgoogle.protobuf.Empty**

如果方法支持响应字段掩码以指定要返回的字段子集,则 ListGetCreateUpdate 方法返回的资源可能包含部分数据。在某些情况下,API 平台对所有方法的字段掩码提供原生支持。

从不立即移除资源的 Delete 方法(例如更新标志或创建长时间运行的删除操作)返回的响应应该包含长时间运行的操作或修改后的资源。

对于在单个 API 调用的时间跨度内未完成的请求,标准方法还可以返回长时间运行的操作

以下部分详细描述了每种标准方法。 这些示例显示了.proto 文件中定义的方法和 HTTP 映射的特殊注释。您可以在 Google API 代码库中找到许多使用标准方法的示例。

列出

List 方法将一个集合名称和零个或多个参数作为输入,并返回与输入匹配的资源列表。

List 通常用于搜索资源。List 适用于来自单个集合的数据,该集合的大小有限且不进行缓存。对于更广泛的情况,应该使用自定义方法 Search

批量 get(例如,获取多个资源 ID 并为每个 ID 返回对象的方法)应该被实现为自定义 BatchGet 方法,而不是 List 方法。但是,如果您有一个已经存在的可提供相同功能的 List 方法,可以出于此目的重复使用 List 方法。如果您使用的是自定义 BatchGet 方法,则应该将其映射到 HTTP GET

适用的常见模式:分页结果排序

适用的命名规则:过滤字段结果字段

HTTP 映射:

  • List 方法 必须使用 HTTP GET 动词。
  • 接收其资源正在列出的集合名称的请求消息字段应该映射到网址路径。如果集合名称映射到网址路径,则网址模板的最后一段(集合 ID必须是字面量。
  • 所有剩余的请求消息字段应该映射到网址查询参数。
  • 没有请求正文,API 配置不得声明 body 子句。
  • 响应正文应该包含资源列表以及可选元数据。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Lists books in a shelf.
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
// List method maps to HTTP GET.
option (google.api.http) = {
// The `parent` captures the parent resource name, such as "shelves/shelf1".
get: "/v1/{parent=shelves/*}/books"
};
}
message ListBooksRequest {
// The parent resource name, for example, "shelves/shelf1".
string parent = 1;
// The maximum number of items to return.
int32 page_size = 2;
// The next_page_token value returned from a previous List request, if any.
string page_token = 3;
}
message ListBooksResponse {
// The field name should match the noun "books" in the method name. There
// will be a maximum number of items returned based on the page_size field
// in the request.
repeated Book books = 1;
// Token to retrieve the next page of results, or empty if there are no
// more results in the list.
string next_page_token = 2;
}

获取

Get 方法需要一个资源名称和零个或多个参数作为输入,并返回指定的资源。

HTTP 映射:

  • Get 方法 必须使用 HTTP GET 动词。
  • 接收资源名称的请求消息字段应该映射到网址路径。
  • 所有剩余的请求消息字段应该映射到网址查询参数。
  • 没有请求正文,API 配置不得声明 body 子句。
  • 返回的资源应该映射到整个响应正文。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Gets a book.
rpc GetBook(GetBookRequest) returns (Book) {
// Get maps to HTTP GET. Resource name is mapped to the URL. No body.
option (google.api.http) = {
// Note the URL template variable which captures the multi-segment resource
// name of the requested book, such as "shelves/shelf1/books/book2"
get: "/v1/{name=shelves/*/books/*}"
};
}
message GetBookRequest {
// The field will contain name of the resource requested, for example:
// "shelves/shelf1/books/book2"
string name = 1;
}

创建

Create 方法需要一个父资源名称、一个资源以及零个或多个参数作为输入。它在指定的父资源下创建新资源,并返回新建的资源。

如果 API 支持创建资源,则应该为每一个可以创建的资源类型设置 Create 方法。

HTTP 映射:

  • Create 方法 必须使用 HTTP POST 动词。
  • 请求消息应该具有字段 parent,以指定要在其中创建资源的父资源名称。
  • 包含资源的请求消息字段必须映射到请求正文。如果将 google.api.http 注释用于 Create 方法,则必须使用 body: "<resource_field>" 表单。
  • 该请求可以包含名为 <resource>_id 的字段,以允许调用者选择客户端分配的 ID。该字段可以在资源内。
  • 所有剩余的请求消息字段应该映射到网址查询参数。
  • 返回的资源应该映射到整个 HTTP 响应正文。

如果 Create 方法支持客户端分配的资源名称并且资源已存在,则请求应该失败并显示错误代码 ALREADY_EXISTS 或使用服务器分配的不同的资源名称,并且文档应该清楚地记录创建的资源名称可能与传入的不同。

Create 方法必须使用输入资源,以便在资源架构更改时,无需同时更新请求架构和资源架构。对于客户端无法设置的资源字段,必须将它们记录为“仅限输出”字段。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Creates a book in a shelf.
rpc CreateBook(CreateBookRequest) returns (Book) {
// Create maps to HTTP POST. URL path as the collection name.
// HTTP request body contains the resource.
option (google.api.http) = {
// The `parent` captures the parent resource name, such as "shelves/1".
post: "/v1/{parent=shelves/*}/books"
body: "book"
};
}
message CreateBookRequest {
// The parent resource name where the book is to be created.
string parent = 1;
// The book id to use for this book.
string book_id = 3;
// The book resource to create.
// The field name should match the Noun in the method name.
Book book = 2;
}
rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
option (google.api.http) = {
post: "/v1/shelves"
body: "shelf"
};
}
message CreateShelfRequest {
Shelf shelf = 1;
}

更新

Update 方法需要一条包含一个资源的请求消息和零个或多个参数作为输入。它更新指定的资源及其属性,并返回更新后的资源。

除了包含资源名称或父资源的属性之外,Update 方法应该可以改变可变资源的属性。Update 方法 不得包含任何“重命名”或“移动”资源的功能,这些功能应该由自定义方法来处理。

HTTP 映射:

  • 标准 Update 方法应该支持部分资源更新,并将 HTTP 动词 PATCH 与名为 update_maskFieldMask字段一起使用。应忽略客户端提供的作为输入的输出字段
  • 需要更高级修补语义的 Update 方法(例如附加到重复字段)应该自定义方法提供。
  • 如果 Update 方法仅支持完整资源更新,则必须使用 HTTP 动词 PUT。但是,强烈建议不要进行完整更新,因为在添加新资源字段时会出现向后兼容性问题。
  • 接收资源名称的消息字段必须映射到网址路径。该字段可以位于资源消息本身中。
  • 包含资源的请求消息字段必须映射到请求正文。
  • 所有剩余的请求消息字段必须映射到网址查询参数。
  • 响应消息必须是更新的资源本身。

如果 API 接受客户端分配的资源名称,则服务器可以允许客户端指定不存在的资源名称并创建新资源。 否则,使用不存在的资源名称的 Update 方法应该失败。 如果这是唯一的错误条件,则应该使用错误代码 NOT_FOUND

具有支持资源创建的 Update 方法的 API 还应该提供 Create 方法。原因是,如果 Update 方法是唯一的方法,则它将不知道如何创建资源。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Updates a book.
rpc UpdateBook(UpdateBookRequest) returns (Book) {
// Update maps to HTTP PATCH. Resource name is mapped to a URL path.
// Resource is contained in the HTTP request body.
option (google.api.http) = {
// Note the URL template variable which captures the resource name of the
// book to update.
patch: "/v1/{book.name=shelves/*/books/*}"
body: "book"
};
}
message UpdateBookRequest {
// The book resource which replaces the resource on the server.
Book book = 1;
// The update mask applies to the resource. For the `FieldMask` definition,
// see https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask
FieldMask update_mask = 2;
}

删除

Delete 方法需要一个资源名称和零个或多个参数作为输入,并删除或计划删除指定的资源。Delete 方法 应该返回 google.protobuf.Empty

API 不应该依赖于 Delete 方法返回的任何信息,因为它不能重复调用。

HTTP 映射:

  • Delete 方法 必须使用 HTTP DELETE 动词。
  • 接收资源名称的请求消息字段应该映射到网址路径。
  • 所有剩余的请求消息字段应该映射到网址查询参数。
  • 没有请求正文,API 配置不得声明 body 子句。
  • 如果 Delete 方法立即移除资源,则应该返回空响应。
  • 如果 Delete 方法启动长时间运行的操作,则应该返回长时间运行的操作。
  • 如果 Delete 方法仅将资源标记为已删除,则应该返回更新后的资源。

Delete 方法的调用在效果上应该是幂等的,但不需要产生相同的响应。任意数量的 Delete 请求都应该导致资源(最终)被删除,但只有第一个请求会产生成功代码。后续请求应生成 google.rpc.Code.NOT_FOUND

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Deletes a book.
rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {
// Delete maps to HTTP DELETE. Resource name maps to the URL path.
// There is no request body.
option (google.api.http) = {
// Note the URL template variable capturing the multi-segment name of the
// book resource to be deleted, such as "shelves/shelf1/books/book2"
delete: "/v1/{name=shelves/*/books/*}"
};
}
message DeleteBookRequest {
// The resource name of the book to be deleted, for example:
// "shelves/shelf1/books/book2"
string name = 1;
}

自定义方法

本章将讨论如何在 API 设计中使用自定义方法。

自定义方法是指 5 个标准方法之外的 API 方法。这些方法应该仅用于标准方法不易表达的功能。通常情况下,API 设计者应该尽可能优先考虑使用标准方法,而不是自定义方法。标准方法具有大多数开发者熟悉的更简单且定义明确的语义,因此更易于使用且不易出错。另一项优势是 API 平台更加了解和支持标准方法,例如计费、错误处理、日志记录、监控。

自定义方法可以与资源、集合或服务关联。 它可以接受任意请求和返回任意响应,并且还支持流式请求和响应。

自定义方法名称必须遵循方法命名惯例

HTTP 映射

对于自定义方法,它们应该使用以下通用 HTTP 映射:

1
https://service.name/v1/some/resource/name:customVerb

使用 : 而不是 / 将自定义动词与资源名称分开以便支持任意路径。例如,恢复删除文件可以映射到 POST /files/a/long/file/name:undelete

选择 HTTP 映射时,遵循以下准则:

  • 自定义方法应该使用 HTTP POST 动词,因为该动词具有最灵活的语义,但作为替代 get 或 list 的方法(如有可能,可以使用 GET)除外。(详情请参阅第三条。)
  • 自定义方法不应该使用 HTTP PATCH,但可以使用其他 HTTP 动词。在这种情况下,方法必须遵循该动词的标准 HTTP 语义
  • 请注意,使用 HTTP GET 的自定义方法必须具有幂等性并且无负面影响。例如,在资源上实现特殊视图的自定义方法应该使用 HTTP GET
  • 接收与自定义方法关联的资源或集合的资源名称的请求消息字段应该映射到网址路径。
  • 网址路径必须以包含冒号(后跟自定义动词)的后缀结尾。
  • 如果用于自定义方法的 HTTP 动词允许 HTTP 请求正文(其适用于 POSTPUTPATCH 或自定义 HTTP 动词),则此自定义方法的 HTTP 配置必须使用 body: "*" 子句,所有其他请求消息字段都映射到 HTTP 请求正文。
  • 如果用于自定义方法的 HTTP 动词不接受 HTTP 请求正文(GETDELETE),则此方法的 HTTP 配置不得使用 body 子句,并且所有其他请求消息字段都映射到网址查询参数。

警告:如果一个服务会实现多个 API,API 生产者必须仔细创建服务配置,以避免 API 之间的自定义动词发生冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// This is a service level custom method.
rpc Watch(WatchRequest) returns (WatchResponse) {
// Custom method maps to HTTP POST. All request parameters go into body.
option (google.api.http) = {
post: "/v1:watch"
body: "*"
};
}
// This is a collection level custom method.
rpc ClearEvents(ClearEventsRequest) returns (ClearEventsResponse) {
option (google.api.http) = {
post: "/v3/events:clear"
body: "*"
};
}
// This is a resource level custom method.
rpc CancelEvent(CancelEventRequest) returns (CancelEventResponse) {
option (google.api.http) = {
post: "/v3/{name=events/*}:cancel"
body: "*"
};
}
// This is a batch get custom method.
rpc BatchGetEvents(BatchGetEventsRequest) returns (BatchGetEventsResponse) {
// The batch get method maps to HTTP GET verb.
option (google.api.http) = {
get: "/v3/events:batchGet"
};
}

用例

自定义方法适用于以下场景:

  • 重启虚拟机。 设计备选方案可能是“在重启集合中创建一个重启资源”,这会让人感觉过于复杂,或者“虚拟机具有可变状态,客户端可以将状态从 RUNNING 更新到 RESTARTING”,这会产生可能存在哪些其他状态转换的问题。 此外,重启是一个常见概念,可以合理转化为一个自定义方法,从直观上来说符合开发者的预期。
  • 发送邮件。 创建一个电子邮件消息不一定意味着要发送它(草稿)。与设计备选方案(将消息移动到“发件箱”集合)相比,自定义方法更容易被 API 用户发现,并且可以更直接地对概念进行建模。
  • 提拔员工。 如果作为标准 update 方法实现,客户端需要复制企业提拔流程管理政策,以确保提拔发生在正确的级别,并属于同一职业阶梯等等。
  • 批处理方法。 对于对性能要求苛刻的方法,提供自定义批处理方法可以有助于减少每个请求的开销。例如,accounts.locations.batchGet

以下是标准方法比自定义方法更适用的示例:

  • 使用不同查询参数的查询资源(使用带有标准列表过滤的标准 list 方法)。
  • 简单的资源属性更改(使用带有字段掩码的标准 update 方法)。
  • 关闭一个通知(使用标准 delete 方法)。

常用自定义方法

以下是常用或有用的自定义方法名称的精选列表。API 设计者在引入自己的名称之前应该考虑使用这些名称,以提高 API 之间的一致性。

方法名称自定义动词HTTP动词备注
取消:cancelPOST取消一个未完成的操作,例如 operations.cancel
batchGet:batchGetPOST批量获取多个资源。如需了解详情,请参阅列表描述
移动:moveGET将资源从一个父级移动到另一个父级,例如 folders.move
搜索:searchPOSTList 的替代方法,用于获取不符合 List 语义的数据,例如 services.search
恢复删除:undeletePOST恢复之前删除的资源,例如 services.undelete。建议的保留期限为 30 天

标准字段

本节介绍了需要类似概念时应使用的一组标准消息字段定义。这将确保相同的概念在不同 API 中具有相同的名称和语义。

名称类型说明
namestringname 字段应包含相对资源名称
parentstring对于资源定义和 List/Create 请求,parent 字段应包含父级相对资源名称
create_timeTimestamp创建实体的时间戳。
update_timeTimestamp最后更新实体的时间戳。注意:执行 create/patch/delete 操作时会更新 update_time。
delete_timeTimestamp删除实体的时间戳,仅当它支持保留时才适用。
expire_timeTimestamp实体到期时的到期时间戳。
start_timeTimestamp标记某个时间段开始的时间戳。
end_timeTimestamp标记某个时间段或操作结束的时间戳(无论其成功与否)。
read_timeTimestamp应读取(如果在请求中使用)或已读取(如果在响应中使用)特定实体的时间戳。
time_zonestring时区名称。它应该是 IANA TZ 名称,例如“America/Los_Angeles”。如需了解详情,请参阅 https://en.wikipedia.org/wiki/List_of_tz_database_time_zones。
region_codestring位置的 Unicode 国家/地区代码 (CLDR),例如“US”和“419”。如需了解详情,请访问 http://www.unicode.org/reports/tr35/#unicode_region_subtag。
language_codestringBCP-47 语言代码,例如“en-US”或“sr-Latn”。如需了解详情,请参阅 http://www.unicode.org/reports/tr35/#Unicode_locale_identifier。
mime_typestringIANA 发布的 MIME 类型(也称为媒体类型)。如需了解详情,请参阅 https://www.iana.org/assignments/media-types/media-types.xhtml。
display_namestring实体的显示名称。
titlestring实体的官方名称,例如公司名称。它应被视为 display_name 的正式版本。
descriptionstring实体的一个或多个文本描述段落。
filterstringList 方法的标准过滤器参数。
querystring如果应用于搜索方法(即 :search),则与 filter 相同。
page_tokenstringList 请求中的分页令牌。
page_sizeint32List 请求中的分页大小。
total_sizeint32列表中与分页无关的项目总数。
next_page_tokenstringList 响应中的下一个分页令牌。它应该用作后续请求的 page_token。空值表示不再有结果。
order_bystring指定 List 请求的结果排序。
request_idstring用于检测重复请求的唯一字符串 ID。
resume_tokenstring用于恢复流式传输请求的不透明令牌。
labelsmap<string, string>表示 Cloud 资源标签。
show_deletedbool如果资源允许恢复删除行为,相应的 List 方法必须具有 show_deleted 字段,以便客户端可以发现已删除的资源。
update_maskFieldMask它用于 Update 请求消息,该消息用于对资源执行部分更新。此掩码与资源相关,而不是与请求消息相关。
validate_onlybool如果为 true,则表示仅应验证给定请求,而不执行该请求。

系统参数

除标准字段外,Google API 还支持可以在所有 API 方法中使用的一组共同请求参数,这些参数称为“系统参数”。如需了解详情,请参阅系统参数

错误

本章简要介绍了 Google API 的错误模型。它还为开发者提供了正确生成和处理错误的通用指南。

Google API 使用简单的协议无关错误模型,以便我们在不同的 API、API 协议(如 gRPC 或 HTTP)和错误上下文(例如异步、批处理或工作流错误)中能够有一致的体验。

错误模型

错误模型由 google.rpc.Status 在逻辑上定义,该实例将在 API 错误发生时返回给客户端。以下代码段显示了错误模型的总体设计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package google.rpc;
message Status {
// A simple error code that can be easily handled by the client. The
// actual error code is defined by `google.rpc.Code`.
int32 code = 1;
// A developer-facing human-readable error message in English. It should
// both explain the error and offer an actionable resolution to it.
string message = 2;
// Additional error information that the client code can use to handle
// the error, such as retry delay or a help link.
repeated google.protobuf.Any details = 3;
}

由于大多数 Google API 采用面向资源的 API 设计,因此错误处理遵循相同的设计原则,使用一小组标准错误配合大量资源。例如,服务器没有定义不同类型的“找不到”错误,而是使用一个标准 google.rpc.Code.NOT_FOUND 错误代码并告诉客户端找不到哪个特定资源。状态空间变小降低了文档的复杂性,在客户端库中提供了更好的惯用映射,并降低了客户端的逻辑复杂性,同时不限制是否包含可操作信息。

错误代码

Google API 必须使用 google.rpc.Code 定义的规范错误代码。单个 API 避免定义其他错误代码,因为开发人员不太可能编写用于处理大量错误代码的逻辑。作为参考,每个 API 调用平均处理 3 个错误代码意味着大多数应用的逻辑只是用于错误处理,这对开发人员而言并非好体验。

错误消息

错误消息应该可以帮助用户轻松快捷地理解和解决 API 错误。通常,在编写错误消息时请考虑以下准则:

  • 不要假设用户是您 API 的专家用户。用户可能是客户端开发人员、操作人员、IT 人员或应用的最终用户。
  • 不要假设用户了解有关服务实现的任何信息,或者熟悉错误的上下文(例如日志分析)。
  • 如果可能,应构建错误消息,以便技术用户(但不一定是 API 开发人员)可以响应错误并改正。
  • 确保错误消息内容简洁。如果需要,请提供一个链接,便于有疑问的读者提问、提供反馈或详细了解错误消息中不方便说明的信息。此外,可使用详细信息字段来提供更多信息。

警告:错误消息不属于 API 表面。它们随时都会更改,恕不另行通知。应用代码不得严重依赖于错误消息。

错误详情

Google API 为错误详细信息定义了一组标准错误负载,您可在 google/rpc/error_details.proto 中找到这些错误负载。 它们涵盖了对于 API 错误的最常见需求,例如配额失败和无效参数。与错误代码一样,开发者应尽可能使用这些标准载荷。

只有在可以帮助应用代码处理错误的情况下,才应引入其他错误详细信息类型。如果错误信息只能由人工处理,则应根据错误消息内容,让开发人员手动处理,而不是引入其他错误详细信息类型。

下面是一些示例 error_details 载荷:

  • RetryInfo:描述客户端何时可以重试失败的请求,这些内容可能在以下方法中返回:Code.UNAVAILABLECode.ABORTED
  • QuotaFailure:描述配额检查失败的方式,这些内容可能在以下方法中返回:Code.RESOURCE_EXHAUSTED
  • BadRequest:描述客户端请求中的违规行为,这些内容可能在以下方法中返回:Code.INVALID_ARGUMENT
  • ErrorInfo 提供既稳定可扩展的结构化错误信息。

错误信息

ErrorInfo 是其中一个标准错误载荷。它提供人类和应用都可以依赖的既稳定又可扩展的错误信息。每个 ErrorInfo 包含 3 条信息:一个错误网域、一个错误原因和一组错误元数据。如需了解详情,请参阅 ErrorInfo 定义。

HTTP 映射

虽然 proto3 消息具有原生 JSON 编码,但 Google 的 API 平台对 Google JSON REST API 使用了不同的错误架构,以实现向后兼容性。

架构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// The error schema for Google REST APIs. NOTE: this schema is not used for
// other wire protocols.
message Error {
// This message has the same semantics as `google.rpc.Status`. It has an extra
// field `status` for backward compatibility with Google API Client Library.
message Status {
// This corresponds to `google.rpc.Status.code`.
int32 code = 1;
// This corresponds to `google.rpc.Status.message`.
string message = 2;
// This is the enum version for `google.rpc.Status.code`.
google.rpc.Code status = 4;
// This corresponds to `google.rpc.Status.details`.
repeated google.protobuf.Any details = 5;
}
// The actual error payload. The nested message structure is for backward
// compatibility with Google API client libraries. It also makes the error
// more readable to developers.
Status error = 1;
}

示例:

1
2
3
4
5
6
7
8
9
10
11
{
"error": {
"code": 401,
"message": "Request had invalid credentials.",
"status": "UNAUTHENTICATED",
"details": [{
"@type": "type.googleapis.com/google.rpc.RetryInfo",
...
}]
}
}

RPC 映射

不同的 RPC 协议采用不同方式映射错误模型。对于 gRPC,生成的代码和每种支持语言的运行时库为错误模型提供原生支持。可在 gRPC 的 API 文档中找到更多信息。例如,请参阅 gRPC Java 的 io.grpc.Status

客户端库映射

Google 客户端库可能会根据语言选择采用不同方式表达错误,以与既定习语保持一致。例如,google-cloud-go 库将返回一个错误,该错误实现与 google.rpc.Status 相同的接口,而 google-cloud-java 将引发异常。

错误本地化

google.rpc.Status 中的 message 字段面向开发人员,必须使用英语。

如果需要面向用户的错误消息,请使用 google.rpc.LocalizedMessage 作为您的详细信息字段。虽然 google.rpc.LocalizedMessage 中的消息字段可以进行本地化,请确保 google.rpc.Status 中的消息字段使用英语。

默认情况下,API 服务应使用经过身份验证的用户的语言区域设置或 HTTP Accept-Language 标头来确定本地化的语言。

处理错误

下面的表格包含在 google.rpc.Code 中定义的所有 gRPC 错误代码及其原因的简短描述。要处理错误,您可以检查返回状态代码的说明并相应地修改您的调用。

HTTPRPC说明
200OK无错误。
400INVALID_ARGUMENT客户端指定了无效参数。如需了解详情,请查看错误消息和错误详细信息。
400FAILED_PRECONDITION请求无法在当前系统状态下执行,例如删除非空目录。
400OUT_OF_RANGE客户端指定了无效范围。
401UNAUTHENTICATED由于 OAuth 令牌丢失、无效或过期,请求未通过身份验证。
403PERMISSION_DENIED客户端权限不足。可能的原因包括 OAuth 令牌的覆盖范围不正确、客户端没有权限或者尚未为客户端项目启用 API。
404NOT_FOUND找不到指定的资源,或者请求由于未公开的原因(例如白名单)而被拒绝。
409ABORTED并发冲突,例如读取/修改/写入冲突。
409ALREADY_EXISTS客户端尝试创建的资源已存在。
429RESOURCE_EXHAUSTED资源配额不足或达到速率限制。如需了解详情,客户端应该查找 google.rpc.QuotaFailure 错误详细信息。
499CANCELLED请求被客户端取消。
500DATA_LOSS出现不可恢复的数据丢失或数据损坏。客户端应该向用户报告错误。
500UNKNOWN出现未知的服务器错误。通常是服务器错误。
500INTERNAL出现内部服务器错误。通常是服务器错误。
501NOT_IMPLEMENTEDAPI 方法未通过服务器实现。
503UNAVAILABLE服务不可用。通常是服务器已关闭。
504DEADLINE_EXCEEDED超出请求时限。仅当调用者设置的时限比方法的默认时限短(即请求的时限不足以让服务器处理请求)并且请求未在时限范围内完成时,才会发生这种情况。

错误重试

客户端可能使用指数退避算法重试 503 UNAVAILABLE 错误。 除非另有说明,否则最小延迟应为 1 秒。

对于 429 RESOURCE_EXHAUSTED 错误,客户端可能会在更高层级以最少 30 秒的延迟重试。此类重试仅对长时间运行的后台作业有用。

对于所有其他错误,重试请求可能并不适用。首先确保您的请求具有幂等性,并查看 google.rpc.RetryInfo 以获取指导。

错误传播

如果您的 API 服务依赖于其他服务,则不应盲目地将这些服务的错误传播到您的客户端。在翻译错误时,我们建议执行以下操作:

  • 隐藏实现详细信息和机密信息。
  • 调整负责该错误的一方。例如,从另一个服务接收 INVALID_ARGUMENT 错误的服务器应该将 INTERNAL 传播给它自己的调用者。

生成错误

如果您是服务器开发人员,则应该生成包含足够信息的错误,以帮助客户端开发人员理解并解决问题。同时,您必须重视用户数据的安全性和隐私性,避免在错误消息和错误详细信息中披露敏感信息,因为错误通常会被记录下来并且可能被其他人访问。例如,“客户端 IP 地址不在许可名单 128.0.0.0/8 上”之类的错误消息会披露服务器端政策的相关信息,而用户可能无法访问这些信息。

要生成正确的错误,首先需要熟悉 google.rpc.Code,然后才能为每个错误条件选择最合适的错误代码。服务器应用可以并行检查多个错误条件,并返回第一个错误条件。

下表列出了每个错误代码和恰当的错误消息示例。

HTTPRPC错误消息示例
400INVALID_ARGUMENT请求字段 x.y.z 是 xxx,预期为 [yyy, zzz] 内的一个。
400FAILED_PRECONDITION资源 xxx 是非空目录,因此无法删除。
400OUT_OF_RANGE参数“age”超出范围 [0,125]。
401UNAUTHENTICATED身份验证凭据无效。
403PERMISSION_DENIED使用权限“xxx”处理资源“yyy”被拒绝。
404NOT_FOUND找不到资源“xxx”。
409ABORTED无法锁定资源“xxx”。
409ALREADY_EXISTS资源“xxx”已经存在。
429RESOURCE_EXHAUSTED超出配额限制“xxx”。
499CANCELLED请求被客户端取消。
500DATA_LOSS请参阅注释。
500UNKNOWN请参阅注释。
500INTERNAL请参阅注释。
501NOT_IMPLEMENTED方法“xxx”未实现。
503UNAVAILABLE请参阅注释。
504DEADLINE_EXCEEDED请参阅备注。

注意:由于客户端无法修复服务器错误,因此生成其他错误详细信息没有任何用处。为避免在错误条件下泄露敏感信息,建议不要生成任何错误消息,而仅生成 google.rpc.DebugInfo 错误详细信息。DebugInfo 专为服务器端的日志记录而设计,不得发送到客户端。

google.rpc 软件包定义了一组标准错误载荷,它们优先于自定义错误载荷。下表列出了每个错误代码及其匹配的标准错误负载(如果适用)。

HTTPRPC建议的错误详细信息
400INVALID_ARGUMENTgoogle.rpc.BadRequest
400FAILED_PRECONDITIONgoogle.rpc.PreconditionFailure
400OUT_OF_RANGEgoogle.rpc.BadRequest
401UNAUTHENTICATED
403PERMISSION_DENIED
404NOT_FOUNDgoogle.rpc.ResourceInfo
409ABORTED
409ALREADY_EXISTSgoogle.rpc.ResourceInfo
429RESOURCE_EXHAUSTEDgoogle.rpc.QuotaFailure
499CANCELLED
500DATA_LOSS
500UNKNOWN
500INTERNAL
501NOT_IMPLEMENTED
503UNAVAILABLE
504DEADLINE_EXCEEDED

命名规则

为了跨众多 API 长期为开发者提供一致的体验,API 使用的名称都具有以下特点:

  • 简单
  • 直观
  • 一致

这包括接口、资源、集合、方法和消息的名称。

由于很多开发者不是以英语为母语,所以这些命名惯例的目标之一是确保大多数开发者可以轻松理解 API。对于方法和资源,我们鼓励使用简单、一致和少量的词汇来命名。

  • API 中使用的名称采用正确的美式英语。例如,使用美式英语的 license、color,而非英式英语的 licence、colour。
  • 为了简化命名,可以使用已被广泛接受的简写形式或缩写。例如,API 优于 Application Programming Interface。
  • 尽量使用直观、熟悉的术语。例如,如果描述移除(和销毁)一个资源,则删除优于擦除。
  • 使用相同的名称或术语命名同样的概念,包括跨 API 共享的概念。
  • 避免名称过载。使用不同的名称命名不同的概念。
  • 避免在 API 的上下文以及范围更大的 Google API 生态系统中使用含糊不清、过于笼统的名称。这些名称可能导致对 API 概念的误解。相反,应选择能准确描述 API 概念的名称。这对定义一阶 API 元素(例如资源)的名称尤其重要。没有需避免名称的明确列表,因为每个名称都必须放在其他名称的上下文中进行评估。实例、信息和服务的名称都曾出现过这类问题。所选择的名称应清楚地描述 API 概念(例如:什么的实例?),并将其与其他相关概念区分开(例如:“alert”是指规则、信号还是通知?)。
  • 仔细考虑使用的名称是否可能与常用编程语言中的关键字存在冲突。您可以使用这些名称,但在 API 审核期间可能会触发额外的审查。因此应明智而审慎地使用。

产品名称

产品名称是指 API 的产品营销名称,例如 Google Calendar API。API、界面、文档、服务条款、对帐单和商业合同等信息中使用的产品名称必须一致。Google API 必须使用产品团队和营销团队批准的产品名称。

下表显示了所有相关 API 名称及其一致性的示例。如需详细了解各自名称及其命名惯例,请参阅本页面下方的详细信息。

API 名称示例
产品名称Google Calendar API
服务名称calendar.googleapis.com
软件包名称google.calendar.v3
接口名称google.calendar.v3.CalendarService
来源目录//google/calendar/v3
API 名称calendar

服务名称

服务名称应该是语法上有效的 DNS 名称(遵循 RFC 1035),可以解析为一个或多个网络地址。公开的 Google API 的服务名称采用 xxx.googleapis.com 格式。例如,Google 日历的服务名称是 calendar.googleapis.com

如果一个 API 是由多项服务组成,则采用更容易发现的命名方式。要做到这点,一种方法是使服务名称共享一个通用前缀。例如,build.googleapis.combuildresults.googleapis.com 服务都属于 Google Build API。

软件包名称

API .proto 文件中声明的软件包名称应该与产品和服务名称保持一致。带有版本的 API 的软件包名称必须以此版本结尾。例如:

1
2
// Google Calendar API
package google.calendar.v3;

与服务无直接关联的抽象 API(例如 Google Watcher API)应该使用与产品名称一致的 proto 软件包名称:

1
2
// Google Watcher API
package google.watcher.v1;

API .proto 文件中指定的 Java 软件包名称必须与带有标准 Java 软件包名称前缀(com.edu.net. 等)的 proto 软件包名称相匹配。例如:

1
2
3
4
package google.calendar.v3;
// Specifies Java package name, using the standard prefix "com."
option java_package = "com.google.calendar.v3";

集合 ID

集合 ID 采用复数和 lowerCamelCase(小驼峰式命名法)格式,并遵循美式英语拼写和语义。例如:eventschildrendeletedEvents

接口名称

为了避免与服务名称(例如 pubsub.googleapis.com)混淆,术语 *“接口名称”*是指在 .proto 文件中定义 service 时使用的名称:

1
2
3
4
5
// Library is the interface name.
service Library {
rpc ListBooks(...) returns (...);
rpc ...
}

您可以将服务名称视为对一组 API 实际实现的引用,而接口名称则是 API 的抽象定义。

接口名称应该使用直观的名词,例如 Calendar 或 Blob。该名称不得与编程语言及其运行时库(如 File)中的成熟概念相冲突。

在极少数情况下,接口名称会与 API 中的其他名称相冲突,此时应该使用后缀(例如 ApiService)来消除歧义。

方法名称

服务可以在其 IDL 规范中定义一个或多个远程过程调用 (RPC) 方法,这些方法需与集合和资源上的方法对应。方法名称采用大驼峰式命名格式并遵循 VerbNoun 的命名惯例,其中 Noun(名词)通常是资源类型。

动词名词方法名称请求消息响应消息
ListBookListBooksListBooksRequestListBooksResponse
GetBookGetBookGetBookRequestBook
CreateBookCreateBookCreateBookRequestBook
UpdateBookUpdateBookUpdateBookRequestBook
RenameBookRenameBookRenameBookRequestRenameBookResponse
DeleteBookDeleteBookDeleteBookRequestgoogle.protobuf.Empty

方法名称的动词部分应该使用用于要求或命令的祈使语气,而不是用于提问的陈述语气。

如果关于 API 子资源的方法名称使用提问动词(经常使用陈述语气表示),则容易让人混淆。例如,要求 API 创建一本书,这显然是 CreateBook(祈使语气),但是询问 API 关于图书发行商的状态可能会使用陈述语气,例如 IsBookPublisherApprovedNeedsPublisherApproval。若要在此类情况下继续使用祈使语气,请使用“check”(CheckBookPublisherApproved) 和“validate”(ValidateBookPublisher) 等命令。

方法名称不应包含介词(例如“For”、“With”、“At”、“To”)。通常,带有介词的方法名称表示正在使用新方法,应将一个字段添加到现有方法中,或者该方法应使用不同的动词。

例如,如果 CreateBook 消息已存在且您正在考虑添加 CreateBookFromDictation,请考虑使用 TranscribeBook 方法。

消息名称

消息名称应该简洁明了。避免不必要或多余的字词。如果不存在无形容词的相应消息,则通常可以省略形容词。例如,如果没有非共享代理设置,则 SharedProxySettings 中的 Shared 是多余的。

消息名称不应包含介词(例如“With”、“For”)。通常,带有介词的消息名称可以通过消息上的可选字段来更好地表示。

请求和响应消息

RPC 方法的请求和响应消息应该分别以带有后缀 RequestResponse 的方法名称命名,除非方法请求或响应类型为以下类型:

  • 一条空消息(使用 google.protobuf.Empty)、
  • 一个资源类型,或
  • 一个表示操作的资源

这通常适用于在标准方法 GetCreateUpdateDelete 中使用的请求或响应。

枚举名称

枚举类型必须使用 UpperCamelCase 格式的名称。

枚举值必须使用 CAPITALIZED_NAMES_WITH_UNDERSCORES 格式。每个枚举值必须以分号(而不是逗号)结尾。第一个值应该命名为 ENUM_TYPE_UNSPECIFIED,因为在枚举值未明确指定时系统会返回此值。

1
2
3
4
5
6
enum FooBar {
// The first value represents the default and must be == 0.
FOO_BAR_UNSPECIFIED = 0;
FIRST_VALUE = 1;
SECOND_VALUE = 2;
}

封装容器

封装 proto2 枚举类型(其中 0 值具有非 UNSPECIFIED 的含义)的消息应该以后缀 Value 来命名,并具有名为 value 的单个字段。

1
2
3
4
5
6
7
enum OldEnum {
VALID = 0;
OTHER_VALID = 1;
}
message OldEnumValue {
OldEnum value = 1;
}

字段名称

.proto 文件中的字段定义必须使用 lower_case_underscore_separated_names 格式。这些名称将映射到每种编程语言的生成代码中的原生命名惯例。

字段名称不应包含介词(例如“for”、“during”、“at”),例如:

  • reason_for_error 应该改成 error_reason
  • cpu_usage_at_time_of_failure 应该改成 failure_time_cpu_usage

字段名称不应使用后置形容词(名词后面的修饰符),例如:

  • items_collected 应该改成 collected_items
  • objects_imported 应该改成 imported_objects

重复字段名称

API 中的重复字段必须使用正确的复数形式。这符合现有 Google API 的命名惯例和外部开发者的共同预期。

时间和时间段

要表示一个与任何时区或日历无关的时间点,应该使用 google.protobuf.Timestamp,并且字段名称应该time(例如 start_timeend_time)结尾。

如果时间指向一个活动,则字段名称应该采用 verb_time 的形式,例如 create_timeupdate_time。请勿使用动词的过去时态,例如 created_timelast_updated_time

要表示与任何日历和概念(如“天”或“月”)无关的两个时间点之间的时间跨度,应该使用 google.protobuf.Duration

1
2
3
4
message FlightRecord {
google.protobuf.Timestamp takeoff_time = 1;
google.protobuf.Duration flight_duration = 2;
}

如果由于历史性或兼容性原因(包括系统时间、时长、推迟和延迟),您必须使用整数类型表示与时间相关的字段,那么字段名称必须采用以下格式:

1
2
3
4
5
xxx_{time|duration|delay|latency}_{seconds|millis|micros|nanos}
message Email {
int64 send_time_millis = 1;
int64 receive_time_millis = 2;
}

如果由于历史性或兼容性原因,您必须使用字符串类型表示时间戳,则字段名称不应该包含任何单位后缀。字符串表示形式应该使用 RFC 3339 格式,例如“2014-07-30T10:43:17Z”。

日期和时间

对于与时区和时段无关的日期,应该使用 google.type.Date,并且该名称应具有后缀 _date。如果日期必须表示为字符串,则应采用 ISO 8601 日期格式 YYYY-MM-DD,例如 2014-07-30。

对于与时区和日期无关的时间,应该使用 google.type.TimeOfDay,并且该名称应具有后缀 _time。如果时间必须表示为字符串,则应采用 ISO 8601 24 小时制格式 HH:MM:SS[.FFF],例如 14:55:01.672。

1
2
3
4
message StoreOpening {
google.type.Date opening_date = 1;
google.type.TimeOfDay opening_time = 2;
}

数量

由整数类型表示的数量必须包含度量单位。

1
xxx_{bytes|width_pixels|meters}

如果数量是条目计数,则该字段应该具有后缀 _count,例如 node_count

列表过滤器字段

如果 API 支持对 List 方法返回的资源进行过滤,则包含过滤器表达式的字段应该命名为 filter。例如:

1
2
3
4
5
6
7
message ListBooksRequest {
// The parent resource name.
string parent = 1;
// The filter expression.
string filter = 2;
}

列表响应

List 方法的响应消息(包含资源列表)中的字段名称必须是资源名称本身的复数形式。例如,CalendarApi.ListEvents() 方法必须为返回的资源列表定义一个响应消息ListEventsResponse,其中包含一个名为 events 的重复字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
service CalendarApi {
rpc ListEvents(ListEventsRequest) returns (ListEventsResponse) {
option (google.api.http) = {
get: "/v3/{parent=calendars/*}/events";
};
}
}
message ListEventsRequest {
string parent = 1;
int32 page_size = 2;
string page_token = 3;
}
message ListEventsResponse {
repeated Event events = 1;
string next_page_token = 2;
}

驼峰式命名法

除字段名称和枚举值外,.proto 文件中的所有定义都必须使用由 Google Java 样式定义的 UpperCamelCase 格式的名称。

名称缩写

对于软件开发者熟知的名称缩写,例如 configspec应该在 API 定义中使用这些缩写,而非完整名称。这将使源代码易于读写。而在正式文档中,应该使用完整名称。示例:

  • config (configuration)
  • id (identifier)
  • spec (specification)
  • stats (statistics)

常见设计模式

空响应

标准的 Delete 方法应该返回 google.protobuf.Empty,除非它正在执行“软”删除,在这种情况下,该方法应该返回状态已更新的资源,以指示正在进行删除。

自定义方法应该有自己的 XxxResponse 消息(即使为空),因为它们的功能很可能会随着时间的推移而增长并需要返回其他数据。

表示范围

表示范围的字段应该使用半开区间和命名惯例 [start_xxx, end_xxx),例如 [start_key, end_key)[start_time, end_time)。通常 C ++ STL 库和 Java 标准库会使用半开区间语义。API 应该避免使用其他表示范围的方式,例如 (index, count)[first, last]

资源标签

在面向资源的 API 中,资源架构由 API 定义。要让客户端将少量简单元数据附加到资源(例如,将虚拟机资源标记为数据库服务器),API 应该 使用 google.api.LabelDescriptor 中描述的资源标签设计模式。

为此,API 设计应该map<string, string> labels 字段添加到资源定义中。

1
2
3
4
message Book {
string name = 1;
map<string, string> labels = 2;
}

长时间运行的操作

如果某个 API 方法通常需要很长时间才能完成,您可以通过适当设计,让其向客户端返回“长时间运行的操作”资源,客户端可以使用该资源来跟踪进度和接收结果。 Operation 定义了一个标准接口来使用长时间运行的操作。 各个 API 不得为长时间运行的操作定义自己的接口,以避免不一致性。

操作资源必须作为响应消息直接返回,操作的任何直接后果都应该反映在 API 中。例如,在创建资源时,即便资源表明其尚未准备就绪,该资源也应该出现在 LIST 和 GET 方法中。操作完成后,如果方法并未长时间运行,则 Operation.response 字段应包含本应直接返回的消息。

操作可以使用 Operation.metadata 字段提供有关其进度的信息。即使初始实现没有填充 metadata 字段,API 也应该为此元数据定义消息。

列表分页

可列表集合应该支持分页,即使结果通常很小。

说明:如果某个 API 从一开始就不支持分页,稍后再支持它就比较麻烦,因为添加分页会破坏 API 的行为。 不知道 API 正在使用分页的客户端可能会错误地认为他们收到了完整的结果,而实际上只收到了第一页。

为了在 List 方法中支持分页(在多个页面中返回列表结果),API 应该

  • List 方法的请求消息中定义 string 字段 page_token。客户端使用此字段请求列表结果的特定页面。
  • List 方法的请求消息中定义 int32 字段 page_size。客户端使用此字段指定服务器返回的最大结果数。服务器可以进一步限制单个页面中返回的最大结果数。如果 page_size0,则服务器将决定要返回的结果数。
  • List 方法的响应消息中定义 string 字段 next_page_token。此字段表示用于检索下一页结果的分页令牌。如果值为 "",则表示请求没有进一步的结果。

要检索下一页结果,客户端应该在后续的 List 方法调用中(在请求消息的 page_token 字段中)传递响应的 next_page_token 值:

1
2
3
4
5
6
7
8
9
10
11
12
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);
message ListBooksRequest {
string parent = 1;
int32 page_size = 2;
string page_token = 3;
}
message ListBooksResponse {
repeated Book books = 1;
string next_page_token = 2;
}

当客户端传入除页面令牌之外的查询参数时,如果查询参数与页面令牌不一致,则服务必须使请求失败。

页面令牌内容应该是可在网址中安全使用的 base64 编码的协议缓冲区。 这使得内容可以在避免兼容性问题的情况下演变。如果页面令牌包含潜在的敏感信息,则应该对该信息进行加密。服务必须通过以下方法之一防止篡改页面令牌导致数据意外暴露:

  • 要求在后续请求中重新指定查询参数。
  • 仅在页面令牌中引用服务器端会话状态。
  • 加密并签署页面令牌中的查询参数,并在每次调用时重新验证并重新授权这些参数。

分页的实现也可以提供名为 total_sizeint32 字段中的项目总数。

列出子集合

有时,API 需要让客户跨子集执行 List/Search 操作。例如,“API 图书馆”有一组书架,每个书架都有一系列书籍,而客户希望在所有书架上搜索某一本书。在这种情况下,建议在子集合上使用标准 List,并为父集合指定通配符集合 ID "-"。对于“API 图书馆”示例,我们可以使用以下 REST API 请求:

1
GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx

注意:选择 "-" 而不是 "*" 的原因是为了避免需要进行 URL 转义。

从子集合中获取唯一资源

有时子集合中的资源具有在其父集合中唯一的标识符。此时,在不知道哪个父集合包含它的情况下使用 Get 检索该资源可能很有用。在这种情况下,建议对资源使用标准 Get,并为资源在其中是唯一的所有父集合指定通配符集合 ID "-"。例如,在 API 图书馆中,如果书籍在所有书架上的所有书籍中都是唯一的,我们可以使用以下 REST API 请求:

1
GET https://library.googleapis.com/v1/shelves/-/books/{id}

响应此调用的资源名称必须使用资源的规范名称,并使用实际的父集合标识符而不是每个父集合都使用 "-"。例如,上面的请求应返回名称为 shelves/shelf713/books/book8141(而不是 shelves/-/books/book8141)的资源。

排序顺序

如果 API 方法允许客户端指定列表结果的排序顺序,则请求消息应该包含一个字段:

1
string order_by = ...;

字符串值应该遵循 SQL 语法:逗号分隔的字段列表。例如:"foo,bar"。默认排序顺序为升序。要将字段指定为降序,应该将后缀 " desc" 附加到字段名称中。例如:"foo desc,bar"

语法中的冗余空格字符是无关紧要的。 "foo,bar desc"" foo , bar desc " 是等效的。

提交验证请求

如果 API 方法有副作用,并且需要验证请求而不导致此类副作用,则请求消息应该包含一个字段:

1
bool validate_only = ...;

如果此字段设置为 true,则服务器不得执行任何副作用,仅执行与完整请求一致的特定于实现的验证。

如果验证成功,则必须返回 google.rpc.Code.OK,并且任何使用相同请求消息的完整请求不得返回 google.rpc.Code.INVALID_ARGUMENT。请注意,由于其他错误(例如 google.rpc.Code.ALREADY_EXISTS 或争用情况),请求可能仍然会失败。

请求重复

对于网络 API,幂等 API 方法是首选,因为它们可以在网络故障后安全地重试。但是,某些 API 方法不能轻易为幂等(例如创建资源),并且需要避免不必要的重复。对于此类用例,请求消息包含唯一 ID(如 UUID),服务器将使用该 ID 检测重复并确保请求仅被处理一次。

1
2
3
// A unique request ID for server to detect duplicated requests.
// This field **should** be named as `request_id`.
string request_id = ...;

如果检测到重复请求,则服务器应该返回先前成功请求的响应,因为客户端很可能未收到先前的响应。

枚举默认值

每个枚举定义必须0 值条目开头,当未明确指定枚举值时,使用该条目。API 必须记录如何处理 0 值。

如果存在共同的默认行为,则应该使用枚举值 0,并且 API 应记录预期的行为。

如果没有共同的默认行为,则枚举值 0 应该被命名为 ENUM_TYPE_UNSPECIFIED 并且在使用时应该使用错误 INVALID_ARGUMENT 拒绝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
enum Isolation {
// Not specified.
ISOLATION_UNSPECIFIED = 0;
// Reads from a snapshot. Collisions occur if all reads and writes cannot be
// logically serialized with concurrent transactions.
SERIALIZABLE = 1;
// Reads from a snapshot. Collisions occur if concurrent transactions write
// to the same rows.
SNAPSHOT = 2;
...
}
// When unspecified, the server will use an isolation level of SNAPSHOT or
// better.
Isolation level = 1;

惯用名称可以用于 0 值。例如,google.rpc.Code.OK 是指定缺少错误代码的惯用方法。在这种情况下,在枚举类型的上下文中,OK 在语义上等同于 UNSPECIFIED

如果存在本质上合理且安全的默认值,则该值可以用于“0”值。例如,BASIC资源视图枚举中的“0”值。

语法规则

在 API 设计中,通常需要为某些数据格式定义简单的语法,例如可接受的文本输入。为了在所有 API 中提供一致的开发者体验并减少学习曲线,API 设计人员必须使用以下扩展巴科斯范式(Extended Backus-Naur Form,简写为“EBNF”)语法的变体来定义这样的语法:

1
2
3
4
5
6
7
Production = name "=" [ Expression ] ";" ;
Expression = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term = name | TOKEN | Group | Option | Repetition ;
Group = "(" Expression ")" ;
Option = "[" Expression "]" ;
Repetition = "{" Expression "}" ;

注意TOKEN 表示在语法之外定义的终端符号。

整数类型

在 API 设计中,不应该使用 uint32fixed32 等无符号整数类型,因为某些重要的编程语言和系统(如 Java,JavaScript 和 OpenAPI)不太支持它们。并且它们更有可能导致溢出错误。另一个问题是,不同的 API 很可能会对同一事件使用不匹配的有符号和无符号类型。

当有符号整数类型用于负值无意义的事物(例如大小或超时)时,值 -1 (且仅有 -1可以用于表示特殊含义,例如文件结尾 (EOF)、无限超时、无限制配额限制或未知年龄。必须明确记录此类用法以避免混淆。如果隐式默认值 0 的行为不是非常明显,API 提供方也应对其进行记录。

部分响应

有时,API 客户端只需要响应消息中的特定数据子集。为了支持此类用例,某些 API 平台为部分响应提供原生支持。Google API Platform 通过响应字段掩码来支持它。任何 REST API 调用都有一个隐式系统查询参数 $fields,它是 google.protobuf.FieldMask 值的 JSON 表示形式。在发送回客户端之前,响应消息将由 $fields 过滤。API 平台会自动为所有 API 方法处理此逻辑。

1
GET https://library.googleapis.com/v1/shelves?$fields=name

资源视图

为了减少网络流量,有时可允许客户端限制服务器应在其响应中返回的资源部分,即返回资源视图而不是完整的资源表示形式。API 中的资源视图支持是通过向方法请求添加一个参数来实现的,该参数允许客户端指定希望在响应中接收的资源视图。

该参数具有以下特点:

  • 应该enum 类型
  • 必须命名为 view

枚举的每个值定义将在服务器的响应中返回资源的哪些部分(哪些字段)。为每个 view 值返回的具体内容是由实现定义的,应该在 API 文档中指定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package google.example.library.v1;
service Library {
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
option (google.api.http) = {
get: "/v1/{name=shelves/*}/books"
}
};
}
enum BookView {
// Not specified, equivalent to BASIC.
BOOK_VIEW_UNSPECIFIED = 0;
// Server responses only include author, title, ISBN and unique book ID.
// The default value.
BASIC = 1;
// Full representation of the book is returned in server responses,
// including contents of the book.
FULL = 2;
}
message ListBooksRequest {
string name = 1;
// Specifies which parts of the book resource should be returned
// in the response.
BookView view = 2;
}

此构造将映射到网址中,例如:

1
GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC

您可以在本设计指南的标准方法一章中找到有关定义方法、请求和响应的更多信息。

ETag

ETag 是一个不透明标识符,允许客户端发出条件请求。 为了支持 ETag,API 应该在资源定义中包含字符串字段 etag,并且其语义必须符合 ETag 的常见用法。通常,etag 包含服务器计算的资源的指纹。如需了解更多详情,请参阅 WikipediaRFC 7232

ETag 可以被强验证或弱验证,其中弱验证的 ETag 以 W/ 为前缀。在本上下文中,强验证意味着具有相同 ETag 的两个资源具有逐字节相同的内容和相同的额外字段(即,内容-类型)。这意味着强验证的 ETag 允许缓存部分响应并在稍后组合。

相反,具有相同的弱验证 ETag 值的资源意味着表示法在语义上是等效的,但不一定逐字节相同,因此不适合字节范围请求的响应缓存。

例如:

1
2
3
4
// This is a strong ETag, including the quotes.
"1a2f3e4d5b6c7c"
// This is a weak ETag, including the prefix and quotes.
W/"1a2b3c4d5ef"

请牢记,引号是 ETag 值的一部分并且必须存在,以符合 RFC 7232。这意味着 ETag 的 JSON 表示法最终会对引号进行转义。例如,ETag 在 JSON 资源正文中表示为:

1
2
3
4
// Strong
{ "etag": "\"1a2f3e4d5b6c7c\"", "name": "...", ... }
// Weak
{ "etag": "W/\"1a2b3c4d5ef\"", "name": "...", ... }

ETag 中允许的字符摘要:

  • 仅限可打印的 ASCII
    • RFC 2732 允许的非 ASCII 字符,但对开发者不太友好
  • 不能有空格
  • 除上述位置外,不能有双引号
  • 遵从 RFC 7232 的推荐,避免使用反斜杠,以防止在转义时出现混淆

输出字段

API 可能希望区分客户端提供的作为输入的字段,以及由服务器返回的仅在特定资源上输出的字段。对于仅限输出的字段,应该记录字段属性。

请注意,如果在请求中设置了或在 google.protobuf.FieldMask 中包括了仅输出字段,则服务器必须接受请求并且不出现错误。服务器必须忽略仅限输出字段的存在及任何提示。此建议的原因是因为客户端经常将服务器返回的资源作为另一个请求的输入重新使用,例如,检索到的 Book 稍后将在 UPDATE 方法中被重新使用。如果对仅限输出字段进行验证,则会导致客户端需要额外清除仅限输出字段。

1
2
3
4
5
message Book {
string name = 1;
// Output only.
Timestamp create_time = 2;
}

单例资源

当只有一个资源实例存在于其父资源中(如果没有父资源,则在 API 中)时,可以使用单例资源。

单例资源必须省略标准的 CreateDelete 方法;在创建或删除父资源时即隐式创建或删除了单例资源(如果没有父资源,则单例资源隐式存在)。必须使用标准的 GetUpdate 方法,以及任意适合您的用例的自定义方法访问该资源。

例如,具有 User 资源的 API 可以将每个用户的设置公开为 Settings 单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
rpc GetSettings(GetSettingsRequest) returns (Settings) {
option (google.api.http) = {
get: "/v1/{name=users/*/settings}"
};
}
rpc UpdateSettings(UpdateSettingsRequest) returns (Settings) {
option (google.api.http) = {
patch: "/v1/{settings.name=users/*/settings}"
body: "settings"
};
}
[...]
message Settings {
string name = 1;
// Settings fields omitted.
}
message GetSettingsRequest {
string name = 1;
}
message UpdateSettingsRequest {
Settings settings = 1;
// Field mask to support partial updates.
FieldMask update_mask = 2;
}

流式半关闭

对于任何双向或客户端流传输 API,服务器应该依赖 RPC 系统提供的、客户端发起的半关闭来完成客户端流。无需定义显式完成消息。

客户端需要在半关闭之前发送的任何信息都必须定义为请求消息的一部分。

网域范围名称

网域范围名称是以 DNS 域名为前缀的实体名称,旨在防止名称发生冲突。当不同的组织以分散的方式定义其实体名称时,这种设计模式很有用。其语法类似于没有架构的 URI。

网域范围名称广泛用于 Google API 和 Kubernetes API,例如:

  • Protobuf Any 类型的表示形式:type.googleapis.com/google.protobuf.Any
  • Stackdriver 指标类型:compute.googleapis.com/instance/cpu/utilization
  • 标签键:cloud.googleapis.com/location
  • Kubernetes API 版本:networking.k8s.io/v1
  • x-kubernetes-group-version-kind OpenAPI 扩展程序中的 kind 字段。

布尔值与枚举与字符串

在设计 API 方法时,您通常会为特定功能(例如启用跟踪或停用缓存)提供一组选择。实现此目的的常用方法是引入 boolenumstring 类型的请求字段。对于给定用例,要使用哪种正确的类型通常不是很明显。推荐的选项如下:

  • 如果我们希望获得固定的设计并且有意不想扩展该功能,请使用 bool 类型。例如 bool enable_tracingbool enable_pretty_print
  • 如果我们希望获得灵活的设计,但不希望该设计频繁更改,请使用 enum 类型。一般的经验法则是枚举定义每年仅更改一次或更少。例如 enum TlsVersionenum HttpVersion
  • 如果我们采用开放式设计或者可以根据外部标准频繁更改设计,请使用 string 类型。必须明确记录支持的值。例如:

数据保留

在设计 API 服务时,数据保留是服务可靠性相当重要的部分。通常,用户数据会被软件错误或人为错误误删。没有数据保留和相应的取消删除功能,一个简单的错误就可能对业务造成灾难性的影响。

通常,我们建议 API 服务采用以下数据保留政策:

  • 对于用户元数据、用户设置和其他重要信息,应保留 30 天的数据。例如,监控指标、项目元数据和服务定义。
  • 对于大量用户内容,应保留 7 天的数据。例如,二进制 blob 和数据库表。
  • 对于暂时性状态或费用昂贵的存储服务,如果可行,应保留 1 天的数据。例如,Memcache 实例和 Redis 服务器。

在数据保留期限期间,可以删除数据而不会丢失数据。如果免费提供数据保留的成本很高,则服务可以提供付费的数据保留。

大型载荷

联网 API 通常依赖于多个网络层作为其数据路径。大多数网络层对请求和响应大小有硬性限制。32 MB 是很多系统中常用的限制。

在设计处理大于 10 MB 的载荷的 API 方法时,我们应该谨慎选择合适的策略,以确保易用性和满足未来增长的需求。对于 Google API,我们建议使用流式传输或媒体上传/下载来处理大型载荷。如使用流式传输,服务器会逐步地同步处理大量数据,例如 Cloud Spanner API。如使用媒体,大量数据会流经大型存储系统(如 Google Cloud Storage),服务器可以异步处理数据,例如 Google Drive API。

文档

本部分介绍了如何向 API 添加内嵌文档。大多数 API 还拥有概览、教程和简要参考文档,这些内容本设计指南并不涉及。如需了解 API、资源和方法命名,请参阅命名惯例

proto 文件中的注释格式

使用常用的 Protocol Buffers // 注释格式向 .proto 文件添加注释。

1
2
3
4
// Creates a shelf in the library, and returns the new Shelf.
rpc CreateShelf(CreateShelfRequest) returns (Shelf) {
option (google.api.http) = { post: "/v1/shelves" body: "shelf" };
}

服务配置中的注释

另一种向 .proto 文件添加文档注释的方法是,您可以在其 YAML 服务配置文件中向 API 添加内嵌文档。如果两个文件中都记录了相同的元素,则 YAML 文件中的文档将优先于 .proto 中的文档。

1
2
3
4
5
6
7
documentation:
summary: Gets and lists social activities
overview: A simple example service that lets you get and list possible social activities
rules:
- selector: google.social.Social.GetActivity
description: Gets a social activity. If the activity does not exist, returns Code.NOT_FOUND.
...

如果您有多个服务使用相同的 .proto 文件,并且您希望提供服务专用文档,则可能需要使用此方法。YAML 文档规则还允许您向 API 说明添加更详细的 overview。但一般首选向 .proto 文件添加文档注释。

与向 .proto 添加注释一样,您可以使用 Markdown 在 YAML 文件注释中提供其他格式设置。

API 说明

API 说明是说明 API 功能的短语(以行为动词开头)。在您的 .proto 文件中,API 说明作为注释添加到相应的 service 中,如以下示例所示:

1
2
3
4
// Manages books and shelves in a simple digital library.
service LibraryService {
...
}

以下是一些 API 说明示例:

  • 与世界各地的朋友分享最新动态、照片、视频等。
  • 访问云托管的机器学习服务,轻松构建响应数据流的智能应用。

资源说明

资源说明是描述资源表示的内容的句子。如果您需要添加更多细节,请使用更多句子。在您的 .proto 文件中,API 说明作为注释添加到相应的消息类型中,如以下示例所示:

1
2
3
4
// A book resource in the Library API.
message Book {
...
}

以下是一些资源说明示例:

  • 用户待办事项列表中的一项任务。每项任务具有唯一的优先级。
  • 用户日历上的一个事件。

字段和参数说明

描述字段或参数定义的名词短语,如以下示例所示:

  • 本系列的主题数量。
  • 经纬度坐标的精度,以米为单位。 必须是非负数。
  • 标记是否为本系列的提交资源返回附件网址值。series.insert 的默认值为 true
  • 投票信息的容器。仅在记录投票信息时出现。
  • 目前未使用或已弃用。

字段和参数说明应该描述哪些值有效和无效。请记住,工程师们会通过一切可能的途径导致服务失败,并且他们无法读取底层代码来澄清任何不清楚的信息。

对于字符串,说明应该描述语法和允许的字符以及任何所需的编码。例如:

  • 集合 [A-a0-9] 中的 1-255 个字符
  • 遵循 RFC 2332 惯例且以 / 开头的有效网址路径字符串。长度上限为 500 个字符。

说明应该指定任何默认值或行为,但可以省略描述实际为 null 的默认值。

如果字段值是必需仅限输入仅限输出,则應該在字段说明开头记录这些值。默认情况下,所有字段和参数都是可选的。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
message Table {
// Required. The resource name of the table.
string name = 1;
// Input only. Whether to validate table creation without actually doing it.
bool validate_only = 2;
// Output only. The timestamp when the table was created. Assigned by
// the server.
google.protobuf.Timestamp create_time = 3;
// The display name of the table.
string display_name = 4;
}

注意:只要可行且有用,字段说明就应该提供示例值。

方法说明

方法说明是一个指明方法效果及其操作资源的句子。它通常以第三人称的现在时态动词(即以“s”结尾的动词)开头。如果需要添加详细信息,请使用更多句子。以下是一些示例:

  • 列出已通过身份验证的用户的日历事件。
  • 使用请求中包含的数据来更新日历事件。
  • 从已通过身份验证的用户的位置历史记录中删除一个位置记录。
  • 在已通过身份验证的用户的位置历史记录中,使用请求中包含的数据创建或更新一个位置记录。如果已存在具有相同时间戳值的位置资源,则所提供的数据会覆盖现有数据。

所有说明的核对清单

确保每个说明都是简短而完整的,能够让用户在没有其他关于该 API 的信息的情况下所理解。在大多数情况下,都有更多需要说明的内容,而不仅仅是重复显而易见的信息。例如,series.insert 方法的说明不应该只是说“插入一个序列”。虽然您的命名应该包含信息,但大多数读者还是会阅读说明,因为他们需要名称之外的更多信息。如果您不确定需要在说明中包括哪些其他内容,请尝试回答以下所有相关问题:

  • 它是什么?
  • 如果成功了它会执行什么操作?如果失败了它会执行什么操作?什么可能导致它失败及如何导致它失败?
  • 它具有幂等性吗?
  • 它的单位是什么?(例如:米、度、像素。)
  • 它接受什么范围的值?此范围是否包含边界值?
  • 它有什么副作用?
  • 应该如何使用它?
  • 可能会导致它失败的常见错误有哪些?
  • 它总是存在吗?(例如:“用于投票信息的容器。仅在记录投票信息时存在。”)
  • 它有默认设置吗?

惯例

本部分列出了文本说明和文档的一些使用惯例。例如,在说明标识符时,请使用“ID”(全部大写),而不是“Id”或“id”。在引用该数据格式时,请使用“JSON”而不是“Json”或“json”。以 code font 显示所有字段/参数名称。将文字字符串值以 code font 表示,并将其放入引号中。

  • ID
  • JSON
  • RPC
  • REST
  • property_name"string_literal"
  • true/false

要求级别

要设置预期或陈述要求级别,请使用以下术语:必须、不得、必需、应、不应、应该、不应该、建议、可以和可选。

如需了解这些术语的含义,请参阅 RFC 2119 中的定义。 建议您在 API 文档中包含 RFC 摘要中的语句。

确定哪些术语既符合您的要求,又能为开发者提供灵活性。如果从技术上来说,您的 API 还支持其他选项,则请勿使用绝对术语(如“必须”)。

语言风格

命名惯例中所述,我们建议在编写文档注释时使用简单、一致的词汇和风格。注释应该易于母语非英语的读者理解,所以应避免行话、俚语、复杂的隐喻、引用流行文化,或包含任何其他不容易翻译的内容。使用友好、专业的风格直接与阅读注释的开发者交流,并尽可能简明扼要。请记住,大多数读者的目的是了解如何使用 API,而不是阅读您的文档!

Protocol Buffers v3

本章将讨论如何在 API 设计中使用 Protocol Buffers。 为了简化开发者体验并提高运行时效率,gRPC API 使用 Protocol Buffers 版本 3 (proto3) 进行 API 定义。

Protocol Buffers 是一种与语言和平台无关的简单接口定义语言 (IDL),用于定义数据结构模式和编程接口。它支持二进制和文本传输格式,并可在不同平台上与许多不同的线路协议结合使用。

Proto3 是 Protocol Buffers 的最新版本,包括对 proto2 的以下更改:

  • 字段存在方法(也称为
1
hasField

)不适用于原初字段。未设置的初始字段具有语言定义的默认值。

  • 消息字段的存在方法仍然可用,可以使用编译器生成的 hasField 方法进行测试,或者与 null 或由实现定义的 sentinel 值进行比较。

  • 用户定义的默认字段值不再可用。

  • 枚举定义必须从枚举值零开始。

  • 必填字段不再可用。

  • 扩展程序不再可用,请改用

1
google.protobuf.Any

  • 由于后向和运行时兼容性原因,特别准许使用 google/protobuf/descriptor.proto

  • 群组语法已被移除。

之所以移除这些功能,是为了使 API 设计更简单、更稳定、性能更高。例如,在记录消息之前通常需要过滤某些字段,例如移除敏感信息。如果这些字段是必填字段,则无法执行此操作。

如需了解详情,请参阅 Protocol Buffers

版本控制

本主题介绍 Google API 使用的版本控制政策。通常,这些政策适用于所有 Google 托管式服务。

有时,您需要对 API 进行不向后兼容(或“间断”)的更改。这些类型的更改可能导致依赖于原始功能的代码出现问题或损坏。

Google API 使用版本控制方案来防止重大更改。此外,Google API 仅在某些稳定性级别下提供某些功能,例如 Alpha 版和 Beta 版组件。

注意:Google Cloud 中的稳定性级别与正式版发布阶段(Alpha 版、Beta 版、GA 版)基本一致,但并不相同。Google Cloud 会对此处所列的内容施加自己的其他预期和承诺。

指南

所有 Google API 接口都必须提供一个主要版本号,该版本号在 protobuf 软件包的末尾编码,并作为 REST API 的 URI 路径的第一部分。如果 API 引入了重大更改(例如删除或重命名字段),则该 API 必须增加其 API 版本号,以确保现有用户代码不会突然中断。

注意:上面使用的术语“主要版本”取自语义版本控制。但是,与传统的语义版本控制不同的是,Google API 不得公开次要或补丁版本号。例如,Google API 使用 v1,而不使用 v1.0v1.1v1.4.2。从用户的角度来看,主要版本可以更新,并且用户无需迁移即可获得新功能。

新的 API 主要版本不得依赖于同一 API 的先前主要版本。某一个 API 可以依赖于其他 API,前提是了解这些 API 的依赖项和稳定性风险。在这种情况下,稳定的 API 版本必须只依赖于其他 API 的最新稳定版本。

在一段时间内,同一 API 的不同版本必须能够在单个客户端应用中同时工作。在该时间段内,客户端可以平稳过渡到新版本。旧版必须必须通过合理且经过良好沟通的弃用期,然后才能关闭。

对于具有 Alpha 版或 Beta 版稳定性的版本,API 必须使用以下两种策略之一在 Protobuf 软件包和 URI 路径的主要版本号后面附加稳定性级别:

  • 基于发布版本的版本控制(推荐)
  • 基于版本的版本控制

基于发布版本的版本控制

稳定版是在给定稳定性级别接收就地更新的长效版本。主版本的每个稳定性级别不超过一个发布版本。在此策略下,您最多可以使用三种发布版本:Alpha 版、Beta 版和稳定版。

Alpha 版和 Beta 版必须附加其稳定性级别,但稳定版不得附加稳定性级别。例如,v1 是稳定版的可接受版本,但 v1betav1alpha 不是。同样,v1betav1alpha 是各自的 Alpha 版和 Beta 版的可接受版本,但 v1 不能作为其中任何一个。每一个发布版本都会获得新的功能和“就地”更新。

Beta 版的功能必须是稳定版功能的超集,而 Alpha 版的功能必须是 Beta 版功能的超集。

弃用 API 功能

API 元素(字段、消息、RPC)可能在任何发布版本中被标记为“已弃用”,表明其已不再使用:

1
2
3
4
5
6
7
// A representation of a scroll.
// Books are now preferred over scrolls.
message Scroll {
option deprecated = true;
// ...
}

弃用的 API 功能不得从 Alpha 版升级到 Beta 版,而不能从 Beta 版升级到稳定版。换句话说,就是功能不得在任何发布版本中“预先弃用”。

Beta 版的功能可以在其弃用足够时间(建议 180)后移除。对于仅存在于 Alpha 版中的功能,您可以选择是否弃用,该功能可能会被移除,恕不另行通知。如果在移除该功能之前它已在 API 的 Alpha 版中弃用,则 API 应该应用相同的注释,并且可以使用其希望使用的任何时间范围。

基于版本的版本控制

单独版本是 Alpha 版或 Beta 版,预计在其功能整合到稳定版之前,会在有限的时间段内提供,在此之后,我们将关停单独版本。使用基于版本的版本控制策略时,API 在每个稳定性级别上可以有任意数量的单独版本。

注意:基于发布版本的策略和基于版本的策略均会就地更新稳定版。即使使用基于版本的策略,也只有一个稳定的发布版本,而不是单独的稳定版本。

Alpha 和 Beta 版必须附加其稳定性级别,后跟递增的版本号。例如 v1beta1v1alpha5。API 应该在其文档(如备注)中记录这些版本的时间顺序。

每个 Alpha 版或 Beta 版可以使用向后兼容的更改进行更新。对于 Beta 版,向后兼容的更新应该通过增加版本号并发布带有更改的新版本来创建。例如,如果当前版本为 v1beta1,则接下来将发布 v1beta2

其功能到达稳定版后,应该关闭 Alpha 和 Beta 版本。Alpha 版本随时都可以关闭,而 Beta 版本应该让用户有一个合理的过渡期;建议为 180 天。

兼容性

本页面详细介绍了版本控制部分提供的重大更改和非重大更改的列表。

何为重大(不兼容)更改,这个问题的答案并不完全明确。此处的指导说明被视为每个可能更改的指示性列表而非完整列表。

此处列出的规则仅涉及客户端兼容性。API 提供方应该了解自己在部署方面的要求,包括实现详细信息的更改。

总体目标是将服务更新到新的次要版本或应用补丁时客户端不应该受影响。尚在研究的影响类型包括:

  • 源兼容性:针对 1.0 编写的代码无法针对 1.1 进行编译
  • 二进制兼容性:针对 1.0 编译的代码无法针对 1.1 客户端库来链接/运行。(详细情况取决于客户端平台;这个问题在不同情况下有所不同。)
  • 线路兼容性:针对 1.0 构建的应用程序无法与 1.1 服务器通信
  • 语义兼容性:一切都在运行,但产生了意想不到的或出乎意料的结果

从另一个角度说:旧客户端应该能与使用同一主要版本号的较新服务器结合使用,当其要更新到新的次要版本时(例如利用新功能),应该可以轻松做到。

除了从基于协议的理论方面来考虑,由于存在涉及生成代码和手写代码的客户端库,因此存在实际考虑因素。测试您正在研究的更改时,应尽可能通过生成新版本的客户端库来测试,并确保其测试仍能通过。

下面的讨论内容将原型消息分为三类:

  • 请求消息(例如 GetBookRequest
  • 响应消息(例如 ListBooksResponse
  • 资源消息(例如 Book,并包括其他资源消息中使用的任何消息)

这些类别的消息具有不同的规则,因为请求消息仅从客户端发送到服务器,响应消息仅从服务器发送到客户端,但资源消息通常可以双向传输。 特别是,可以更新的资源需要从读取/修改/写入周期方面来考虑。

向后兼容的(非重大)更改

向 API 服务定义添加 API 接口

从协议的角度来看,这始终比较安全。唯一需要注意的是,客户端库可能已经使用了手写代码中的新 API 接口名称。如果您的新接口与现有接口完全正交,则不太可能实现;如果它是现有接口的简化版本,则更有可能导致冲突。

向 API 接口添加方法

除非您添加的方法与客户端库中已生成的方法发生冲突,否则这应该没问题。

(可能造成重大后果的例子:如果您有 GetFoo 方法,C# 代码生成器已经创建了 GetFooGetFooAsync 方法。因此,从客户端库的角度来看,在 API 接口中添加 GetFooAsync 方法将是一个重大更改。)

向方法添加 HTTP 绑定

假设绑定没有引入任何歧义,让服务器响应之前拒绝的网址就是安全的。将现有操作应用于新资源名称模式时,可以执行此操作。

向请求消息添加字段

添加请求字段可以是非重大更改,前提是未指定该字段的客户端将在新版本中采用与旧版本相同的处理方式。

可能错误地执行此操作的最明显示例是使用分页:如果 API 的 v1.0 不包含集合的分页,则无法在 v1.1 中添加它,除非将默认的 page_size 视为无限(这通常是一个坏主意)。否则,希望通过单个请求获得完整结果的 v1.0 客户端可能只收到部分结果,而且不会意识到该集合包含更多资源。

向响应消息添加字段

并非资源(例如 ListBooksResponse)的响应消息可在不影响客户端的情况下进行扩展,前提是这样不会改变其他响应字段的行为。之前在响应中填充的任何字段都应继续使用相同的语义填充,即使这会引入冗余也如此。

例如,在 1.0 版中的查询响应可能包含 contained_duplicates 的布尔字段,这表示某些结果由于复制而省略。在 1.1 版中,我们可能会在 duplicate_count 字段提供更详细的信息。尽管它在 1.1 版本中是多余的,但 contained_duplicates 字段仍必须填充。

向枚举添加值

只能在请求消息中使用的枚举可以自由扩展以包含新元素。例如,使用资源视图模式可在新的次要版本中添加新视图。客户永远不需要接收这个枚举,因此他们不必知道他们不关心的值。

对于资源消息和响应消息,默认假设客户端应该处理他们不知道的枚举值。但是,API 提供方应该知道编写应用来正确处理新的枚举元素可能很困难。API 所有者应该在遇到未知枚举值时记录预期的客户端行为。

借助 Proto3,客户端可以接收它们不知道的值并重新序列化保持相同值的消息,这样才不会影响读取/修改/写入周期。使用 JSON 格式可在值“名称”未知的情况下发送数值,但服务器通常不知道客户端是否真的知道特定值。因此,JSON 客户端可能知道它们已收到以前未知的值,但它们只会看到名称或数字 - 他们不会同时知道这两者。 在读取/修改/写入循环中将相同的值返回到服务器,此时不应修改该字段,因为服务器应该理解这两种形式。

添加仅限输出的资源字段

可以添加只能由服务器提供的资源实体中的字段。服务器可以验证请求中任何客户端提供的值是否有效,但不能在该值省略时失败。

向后不兼容的(重大)的更改

移除或重命名服务、字段、方法或枚举值

从根本上看,如果客户端代码可能引用某些内容,对其执行移除或重命名操作就是重大更改,必须通过新的主要版本进行。引用旧名称的代码,对于有些语言(例如 C#和 Java)会导致编译失败,对于其他语言则可能导致执行失败或数据丢失。传输格式兼容性与此无关。

更改 HTTP 绑定

此处的“更改”实际上是“删除和添加”。例如,如果您确定确实要支持 PATCH,但您发布的版本支持 PUT,或者您使用了错误的自定义动词名称,则可以添加新绑定,但不能因为相同原因而删除旧绑定,因为移除服务方法是一个重大更改。

更改字段的类型

即使新类型与传输格式兼容,这也可能更改客户端库生成的代码,因此必须通过新的主要版本进行。对于已编译的静态类型语言,这很容易引入编译时错误。

更改资源名称格式

资源不得更改其名称 - 这意味着不能更改集合名称。

与大多数重大更改不同,这也会影响主要版本:如果客户端可以使用 v2.0 访问在 v1.0 中创建的资源(反之亦然),则应在两个版本中使用相同的资源名称。

较容易忽略的是,由于以下原因,有效资源名称集也不应更改:

  • 如果它的限制变得更严格,之前成功的请求现在将失败。
  • 如果它没有之前记录的限制严格,基于先前文档做出假设的客户端可能无法正常工作。客户很可能采用对允许的字符集和名称长度敏感的方式,将资源名称存储在其他位置。或者,客户很可能执行自己的资源名称验证以遵循文档说明。(例如,在开始支持更长的 EC2 资源 ID 之前,亚马逊为客户提供了大量警告并且有一个迁移期。)

请注意,此类更改可能仅在原型文档中可见。 因此,在审核 CL 是否损坏时,仅查看非评论更改并不够。

更改现有请求的可见行为

客户通常依赖 API 行为和语义,即使此类行为没有得到明确支持或记录。因此,在大多数情况下,更改 API 数据的行为或语义造成的影响将被视为使用者的责任。如果行为未以加密方式隐藏,则假设用户已发现并将依赖此行为。

由于这个原因(即使数据很无趣),对分页令牌加密也是一个好主意,可以防止用户创建自己的令牌,以及在令牌行为发生更改时影响令牌。

更改 HTTP 定义中的网址格式

除了上面列出的资源名称更改之外,此处需要考虑两种更改:

  • 自定义方法名称:虽然并非资源名称的一部分,但自定义方法名称是 REST 客户端发布到的网址的一部分。更改自定义方法名称不应该影响 gRPC 客户端,但公共 API 必须假定它们具有 REST 客户端。
  • 资源参数名称:从 v1/shelves/{shelf}/books/{book} 更改为v1/shelves/{shelf_id}/books/{book_id} 不会影响已替代的资源名称,但可能会影响代码生成。

向资源消息添加读取/写入字段

客户端通常会执行读取/修改/写入操作。大多数客户端不会为它们不知道的字段提供值,特别是 proto3,它不支持此操作。您可以指定消息类型(而不是原始类型)的任何缺失字段都表示更新未应用于这些字段,但这使得从实体中显式移除此类字段值变得更加困难。 原初类型(包括 stringbytes)根本无法采用这种方式处理,因为在 proto3 中,将 int32 字段明确指定为 0 与完全不指定没有区别。

如果使用字段掩码执行所有更新,这就不是问题,因为客户端不会隐式覆盖它不知道的字段。但是,这个 API 决策并不寻常:大多数 API 都支持“整个资源”更新。

目录结构

API 服务通常使用 .proto 文件来定义 API 接口,并使用 .yaml 文件来配置 API 服务。每个 API 服务必须在 API 代码库中有一个 API 目录。API 目录应该包含所有 API 定义文件和构建脚本。

每个 API 目录应该具有以下标准布局:

  • API 目录
    • 代码库必要条件
      • BUILD:构建文件。
      • METADATA:构建元数据文件。
      • OWNERS:API 目录所有者。
      • README.md:有关 API 服务的常规信息。
    • 配置文件
      • {service}.yaml:基准服务配置文件,google.api.Service proto 消息的 YAML 表示法。
      • prod.yaml:生产环境增量服务配置文件。
      • staging.yaml:模拟环境增量服务配置文件。
      • test.yaml:测试环境增量服务配置文件。
      • local.yaml:本地环境增量服务配置文件。
    • 文档文件
      • doc/*:技术文档文件。它们应采用 Markdown 格式。
    • 接口定义
      • v[0-9]*/*:每个这样的目录都包含 API 的主要版本,主要是 proto 文件和构建脚本。
      • {subapi}/v[0-9]*/*:每个 {subapi} 目录都包含子 API 的接口定义。每个子 API 可以有自己的独立主要版本。
      • type/*:proto 文件,包含在不同 API 之间、同一 API 的不同版本之间或 API 与服务实现之间共享的类型。type/* 下的类型定义一旦发布就不应该有破坏性更改。

公共 Google API 定义在 GitHub 上发布,请参阅 Google API 代码库。如需详细了解目录结构,请参阅 Service Infrastructure 示例 API

注意:如果您是 Cloud Endpoints 开发者,则可以按照配置 gRPC 服务的说明配置您的 API 服务。

文件结构

gRPC API 应该使用 proto3 IDL 在 .proto 文件中定义。

文件结构必须将更高级别和更重要的定义置于较低级别和较不重要的项目之前。在每个 proto 文件中,适用的部分按以下顺序排列:

  • 版权和许可声明(如果需要)。
  • 采用该顺序的 Proto syntaxpackageimportoption 语句。
  • API 概览文档,为读取器准备文件的其余部分。
  • 按重要性降序排列的 API proto service 定义。
  • 与相应方法采用相同顺序的 RPC 请求和响应 message 定义。每个请求消息必须在其相应的响应消息之前(如果有)。
  • 资源 message 定义。父资源必须在其子资源之前定义。

如果单个 proto 文件包含整个 API 接口,在 API 之后命名:

APIProto
Librarylibrary.proto
Calendarcalendar.proto

大 .proto 文件可能会拆分为多个文件。应根据需要将服务、资源消息和请求/响应消息移到单独的文件中。

我们建议每个服务及其相应的请求和响应使用一个文件。请考虑将此文件命名为 <enclosed service name>.proto。 对于仅包含资源的 proto 文件,请考虑将此文件简单命名为 resources.proto

Proto 文件名

Proto 文件名应该使用 lower_case_underscore_separated_names,并且必须使用扩展名 .proto。例如:service_controller.proto

Proto 选项

为了在不同 API 中生成一致的客户端库,API 开发人员必须在其 .proto 文件中使用一致的 proto 选项。符合本指南的 API 定义必须使用以下文件级 proto 选项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
syntax = "proto3";
// The package name should start with the company name and end with
// the major version.
package google.abc.xyz.v1;
// This option specifies the namespace to be used in C# code. This defaults
// to the PascalCased version of the proto package, which is fine if the
// package name consists of single-word segments.
// For example, a package name of "google.shopping.pets.v1" would use a C#
// namespace of "Google.Shopping.Pets.V1".
// However, if any segment of a package name consists of multiple words,
// this option needs to be specified to avoid only the first word being
// capitalized. For example, a Google Pet Store API might have a package name of
// "google.shopping.petstore.v1", which would mean a C# namespace of
// "Google.Shopping.Petstore.V1". Instead, the option should be used to
// capitalize it properly as "Google.Shopping.PetStore.V1".//
// For more detail on C#/.NET capitalization rules, see the [Framework Design
// Guidelines](https://msdn.microsoft.com/en-us/library/ms229043).//
// One corner-case of capitalization: while acronyms are generally
// PascalCased (e.g. Http), two-letter acronyms are normally all in capitals,
// e.g. `IOStream` and `OSVersion`, not `IoStream` and `OsVersion`. However,
// in APIs this should be used carefully, as protoc doesn't know which words
// are abbreviations and which aren't: it would introduce inconsistency to have
// a namespace of (say) `OSLogin` but then a class called `OsDetails` generated
// from a message of the same name. Unless you can be certain that the acronym
// won't crop up in a message or field name, it's safest to stick to regular
// PascalCase.
// For pre-releases, the Alpha/Beta should also be capitalized, so "V1Beta1"
// rather than "V1beta1" for example.
option csharp_namespace = "Google.Abc.Xyz.V1";
// This option lets the proto compiler generate Java code inside the package
// name (see below) instead of inside an outer class. It creates a simpler
// developer experience by reducing one-level of name nesting and be
// consistent with most programming languages that don't support outer classes.option java_multiple_files = true;
// The Java outer classname should be the filename in UpperCamelCase. This
// class is only used to hold proto descriptor, so developers don't need to
// work with it directly.
option java_outer_classname = "XyzProto";
// The Java package name must be proto package name with proper prefix.
option java_package = "com.google.abc.xyz.v1";
// A reasonable prefix for the Objective-C symbols generated from the package.
// It should at a minimum be 3 characters long, all uppercase, and convention
// is to use an abbreviation of the package name. Something short, but
// hopefully unique enough to not conflict with things that may come along in
// the future. 'GPB' is reserved for the protocol buffer implementation itself.
option objc_class_prefix = "GABCX";
// This option specifies the namespace to be used in PHP code. This defaults
// to the PascalCased version of the proto package, which is fine if the
// package name consists of single-word segments.
// For example, a package name of "google.shopping.pets.v1" would use a PHP
// namespace of "Google\\Shopping\\Pets\\V1".
// However, if any segment of a package name consists of multiple words,
// this option needs to be specified to avoid only the first word being
// capitalized. For example, a Google Pet Store API might have a package name of
// "google.shopping.petstore.v1", which would mean a PHP namespace of
// "Google\\Shopping\\Petstore\\V1". Instead, the option should be used to
// capitalize it properly as "Google\\Shopping\\PetStore\\V1".//
// For pre-releases, the Alpha/Beta should not be capitalized, so "V1beta1"
// rather than "V1Beta1" for example. Note that this is different from the
// capitalization used in the csharp_namespace option for pre-releases.
option php_namespace = "Google\\Abc\\Xyz\\V1";

术语库

联网 API

  • 跨计算机网络运行的应用编程接口。它们使用包括 HTTP 在内的各种网络协议进行通信,并且由不同的组织生成,通常供大于求。

Google API

  • Google 服务公开的联网 API。其中大多数都托管在 googleapis.com 域名上。此术语不包括其他类型的 API,例如客户端库和 SDK。

    所有 Google API 都共享一个通用的使用模型:API 使用者使用由 API 生产者管理的 API 服务。这样一来,用户即可在所有 Google API 中获得简单一致的体验。

Google Cloud API

  • Google Cloud 服务公开的 Google API 子集。如需了解详情,请参阅 Cloud API

API 接口

  • 一个 Protocol Buffers service 定义。它通常映射到大多数编程语言中的某一个接口。API 接口可以由任意数量的 API 服务实现。

API 版本

  • 一个 API 接口或一组 API 接口(如果一起定义)的版本。API 版本通常由字符串(例如“v1”)表示,并显示在 API 请求和 Protocol Buffers 包名称中。

API 方法

  • API 接口中的单个操作。它在 Protocol Buffers 中由 rpc 定义表示,并且通常映射到大多数编程语言的接口中的一个函数。

API 请求

  • API 方法的单个调用。它通常用作计费、日志记录、监控和速率限制的单位。

API 服务

  • 在一个或多个网络地址上公开的一个或多个 API 接口(如 Cloud Pub/Sub API)的部署实现。

    对于 Google API,API 和“服务” 的概念用作 API 服务的同义词。其列在 Google Cloud Console 中的 API 库下。

API 服务名称

API 服务端点

  • 是指 API 服务用于响应实际 API 请求的网络地址。一个 API 服务可能有多个 API 服务端点,例如 https://pubsub.googleapis.comhttps://content-pubsub.googleapis.com

API 服务定义

  • 用于定义 API 服务的 API 接口定义(.proto 文件)和 API 服务配置(.yaml 文件)的组合。 Google API 服务定义的架构为 google.api.Service

API 产品

  • API 服务及相关组件(如服务条款、文档、客户端库和服务支持)将作为一个产品统一呈现给客户。例如,Google Calendar API。人们有时将 API 产品简称为 API。

API 使用者

  • 使用 API 服务的实体。对于 Google API,使用者通常是拥有客户端应用或服务器资源的 Google 项目。

API 生产者

  • 生产 API 服务的实体。对于 Google API,生产者通常是拥有 API 服务的 Google 项目。

API 后端

  • 实现 API 服务业务逻辑的一组服务器及相关基础架构。单个 API 后端服务器通常称为 API 服务器。

API 前端

  • 跨 API 服务提供通用功能(例如负载平衡和身份验证)的一组服务器及相关基础架构。单个 API 前端服务器通常称为 API 代理。

    请注意,API 前端和 API 后端运行时可以彼此相邻,也可以离得很远。在某些情况下,它们可以编译为单个应用二进制文件,并在单个进程中运行。

最后

----本文结束 感谢您的阅读----