Google API权威指南中文版
去年有段时间得空,就把谷歌GAE的API权威指南看了一遍,收获颇丰,特别是在自己几乎独立开发了公司的云数据中心之后,感触更深。
公司的云数据中心支撑着600多应用(截止2017年7月),应用种类繁多和开发者也不尽相同,虽然有小明做了统一的SDK,但是依然让我深深地感觉到TMD不仅仅是界面,API也是很考验作者用户体验设计能力的。
所以,现在将API权威指南翻译出来,与大家分享。英语水平有限,勿怪哈。
介绍
这是网络API的一般设计指南。自2014年以来,它已在Google内部使用,是Google在设计Cloud API和其他Google API时所遵循的指南。本设计指南在此共享,以通知外部开发人员,并使我们所有人更容易合作。
Google Cloud Endpoints开发人员可能会发现本指南在设计gRPC API时特别有用,我们强烈建议此类开发人员使用这些设计原则。但是,我们不要求使用它。您可以使用Cloud Endpoints和gRPC,而无需遵循指南。
本指南适用于REST API和RPC API,特别关注gRPC API。 gRPC API使用Protocol Buffersto定义其API表面和API服务配置来配置其API服务,包括HTTP映射,日志记录和监视。 HTTP映射功能由Google API和云端点用于JSON / HTTP到协议缓冲区/ RPC转码的gRPC API使用。
本指南是生活文件,随着时间的推移,新的风格和设计模式得到采纳和批准。以这种精神,它永远不会是完整的,而且API设计的艺术和手艺将永远有足够的空间。
本文档的使用约定
要求等级关键字“MUST”,“MUST NOT”,“REQUIRED”,“SHALL”,“SHALL NOT”,“SHOULD”,“SHOULD NOT”,“RECOMMENDED”,“MAY”和“OPTIONAL” 文档将被解释为RFC 2119中所述。
在本文档中,使用粗体字体突出显示这些关键字。
面向资源设计
本指南的总目标是为了帮助开发者设计简单、易用和风格一致性的网络API。 At the same time, it also helps converging designs of socket-based RPC APIs with HTTP-based REST APIs.
传统上,人们根据API接口和方法(如CORBA和Windows COM)设计RPC API。 随着时间的推移,越来越多的接口和方法被引入。 最终的结果可能是出现了大量的接口和方法,它们每个都与其他方法不同。 开发人员必须仔细地学习每一个,才能正确使用它,这既费时又容易出错。
REST架构风格第一次出现是在2000年左右, 主要目的是为了让在HTTP/1.1协议上更有效的工作。 它的核心思想是定义那些可以使用少量操作方法来操作的命名资源。资源和方法,就如同API的 名词 和 动词 .结合HTTP协议, 资源名称其实就已经可以映射为一个URL了, 而方法,具体来说就是常见的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方法)的标准方法是列表,获取,创建,更新和删除。 自定义方法(也称为自定义动词或自定义操作)也可用于API设计人员的功能,这些功能不容易映射到诸如数据库事务之类的标准方法之一。
注意:自定义动词并不意味着创建自定义HTTP动词以支持自定义方法。 对于基于HTTP的API,它们只是映射到最合适的HTTP动词。
设计流程
《设计指南》建议参照以下步骤设计面向资源的APIs:
-
确定API提供的资源类型;
-
确定资源之间的关系;
-
基于资源的类型和关系确定资源的命名方式;
-
决定资源模式;
-
给资源附上最小化的方法集。
Resources(资源,直接用英文会更好)
一个面向资源的API 通常都是按资源层级来建模的, 各层级的节点被称为资源或者资源集合。 简单起见, 我们往往分别称之为资源或者集合。
-
一个集合包含一组相同类型的资源。比如,一个用户的所有联系人就是一个资源集合。
-
一个资源会有一些状态,也可能拥有几个子资源。每个子资源都可以表示一个资源或者一个资源集合。
Gmail的API拥有一组用户资源,每个用户又拥有消息资源集合,线程资源集合,标签资源集合等,一个描述资源和多个设置资源等。
虽然存储系统和REST API之间存在一些概念上的一致性,但面向资源的API的服务并不一定是数据库,在解释资源和方法方面具有很大的灵活性。 例如,创建一个日历事件(resource)可以为参与者创建额外的事件,发送电子邮件邀请到出席者,预订会议室,以及更新视频会议日程。
Methods
面向资源的API的关键特性是它强调资源(数据模型),而不是在资源(功能)上执行的方法。一个典型的面向资源的API使用少量的方法公开大量资源。这些方法可以是标准方法或者自定义方法。在本文中, 标准方法有: List
, Get
, Create
, Update
, and Delete
.
如果API的功能可以自然地映射到某一标准方法,那么该方法应该在API设计中使用。对于不能自然映射到标准方法的功能,可以使用自定义方法。自定义方法提供了与传统RPC API相同的设计自由度,这些api可用于实现常见的编程模式,如数据库事务或数据分析。
Examples
下面几节介绍了如何将面向资源的API设计应用于大型服务的一些实际示例。
Gmail API
Gmail API服务实现了Gmail API,并暴露了Gmail的大部分功能。它有以下资源模型:
-
Gmail API 服务:
gmail.googleapis.com
-
users的API集合:
users/*
. 每个用户都拥有以下资源。A collection of messages:users/*/messages/*
.A collection of threads:users/*/threads/*
.A collection of labels:users/*/labels/*
.A collection of change history:users/*/history/*
.A resource representing the user profile:users/*/profile
.A resource representing user settings:users/*/settings
.
Google Cloud Pub/Sub API
pubsub.googleapis.com
服务实现了Google Cloud的订阅发布API,定义了以下资源模型:
-
API服务:
pubsub.googleapis.com
-
主题集合:
projects/*/topics/
*。 -
订阅集合:
projects/*/subscriptions/*
。
注意:Pub / Sub API的其他实现可以选择不同的资源命名方案。
资源命名
在面向资源的api中,资源被命名为实体,资源名是它们的标识符。每个资源都有自己独特的资源名。资源名是由资源本身的ID、任何父资源的ID和它的API服务名组成的。我们将在下面章节研究资源ID,以及如何构造资源名。
gRPC api应该为资源名使用无模式的uri。它们通常遵循REST的URL约定,并且表现得很像网络文件路径。它们可以很容易地映射到REST url:请参阅标准方法部分以获得详细信息。
集合是一种特殊类型的资源,它包含相同类型的子资源列表。例如,目录是文件资源的集合。集合的资源ID称为集合ID。
资源名称使用集合ID和资源ID进行分层组织,以正斜杠分隔。 如果一个资源包含一个子资源,子资源的名称是通过指定父资源名称后跟子资源的ID来形成的,再次用正斜杠分隔。
Example 1: 存储服务有一组 buckets
,每个 buckets
都有一个对象集合:
API Service Name | Collection ID | Resource ID | Collection ID | Resource ID |
---|---|---|---|---|
//storage.googleapis.com | /buckets | /bucket-id | /objects | /object-id |
Example 2: 电子邮件服务有一组用户。 每个用户都有一个设置子资源,并且设置子资源有许多其他子资源,包括customFrom
:
API Service Name | Collection ID | Resource ID | Resource ID | Resource ID |
---|---|---|---|---|
//mail.googleapis.com | /users | /name@example.com | /settings | /customFrom |
只要在资源层次结构中它们是唯一的,API生产者可以为资源和集合ID选择任何可接受的值。 您可以在下面找到更多有关选择适当的资源和集合ID的准则。
通过拆分资源名称,例如name.split(“/”)[n],假设没有一个段包含任何正斜杠,可以获取各个集合ID和资源ID。
完整的资源名称
由DNS兼容的API服务名称和资源路径组成的无方案URI。 资源路径也称为相对资源名称。 例如:
"//library.googleapis.com/shelves/shelf1/books/book2"
API服务名称用于客户端定位API服务端点; 它可能是内部服务的假DNS名称。 如果API服务名称从上下文中显而易见,则通常使用相对的资源名称。
相对路径资源名称
没有前导“/”的URI路径(path-noscheme)。 它标识API服务中的资源。 例如:
"shelves/shelf1/books/book2"
资源ID
识别其父资源中资源的非空URI段(segment-nz-nc),请参见上述示例。
资源名称中的尾随资源ID可能有多个URI段。 例如:
Collection ID | Resource ID |
---|---|
files | /source/py/parser.py |
API服务应在可行时使用URL友好的资源ID。 资源ID必须清楚记录,无论客户端,服务器还是由客户端分配。 例如,文件名通常由客户机分配,而电子邮件消息ID通常由服务器分配。
集合ID
标识其父资源中的集合资源的非空URI段(segment-nz-nc),请参见上述示例。
因为集合ID通常出现在生成的客户端库中,所以它们必须符合以下要求:
-
Must是有效的C / C ++标识符。
-
Must是复数形式与lowerCamel case。
-
Must使用清晰简明的英文术语。
-
应避免或超出合格范围。 例如,
RowValue
优于Value
。 应该避免以下条款,无需资格:ElementEntryInstanceItemObjectResourceTypeValue
资源名 vs URL
虽然完整的资源名称与普通URL相似,但它们并不一样。 单一资源可以由不同版本的API和不同的API协议公开。 完整的资源名称不指定此类信息,因此必须将其映射到特定协议和API版本以供实际使用。
要通过REST API使用完整的资源名称,必须通过在服务名称之前添加HTTPS方案,在资源路径之前添加API主版本以及将URL转义为资源路径,将其转换为REST URL。 例如:
// calendar事件资源名称.
"//calendar.googleapis.com/users/john smith/events/123"
// 相应的HTTP URL.
"https://calendar.googleapis.com/v3/users/john%20smith/events/123"
Resource Name as String
Google API必须使用字符串来表示资源名称,除非是向后兼容性问题。 资源名称应该像普通文件路径一样处理,并且不支持%-encoding。
对于资源定义,第一个字段应该是资源名称的字符串字段,它应该被叫作name
.
Note: 其他相关的name字段应该避免混淆,比如 display_name
, first_name
, last_name
, full_name
.
例如:
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;
}
Note: 为了资源名称的一致性,前导正斜杠不能被任何URL模板变量捕获。 例如,必须使用URL模板"/v1/{name=shelves/*/books/*}"
来代替"/v1{name=/shelves/*/books/*}"
。
标准方法
本章节来定义标准方法的概念,这些方法是: List
, Get
, Create
, Update
, and Delete
. 标准方法降低复杂性并提高一致性。超过70% 的Google API方法都是标准方法 , 这使得他们更加易于学习和使用。
下表介绍了如何将标准方法映射到HTTP方法:
Standard Method | HTTP Mapping | HTTP Request Body | HTTP Response Body |
---|---|---|---|
List |
GET <collection URL> |
Empty | Resource* list |
Get |
GET <resource URL> |
Empty | Resource* |
Create |
POST <collection URL> |
Resource | Resource* |
Update |
PUT or PATCH <resource URL> |
Resource | Resource* |
Delete |
DELETE <resource URL> |
Empty | Empty** |
*从 List
, Get
, Create
和 Update
方法返回的资源可能包含部分数据,如果方法支持字段掩码,指定要返回的字段的子集。 在某些情况下,API平台本身支持所有方法的字段掩码。
Delete
方法的返回响应,并不是立即删除资源(例如更新标志或创建长时间运行的删除操作)的Delete方法返回的响应应该包含长时间运行的操作或修改的资源。
对于在单个API调用的时间范围内未完成的请求,标准方法也可能返回长时间运行的操作。
以下部分详细介绍了每种标准方法。 示例显示.proto文件中定义的方法,其中包含HTTP映射的特殊注释。 您可以在Google API资源库中找到使用标准方法的许多示例。
List
List
方法将集合名称和零个或多个参数作为输入,并返回与输入匹配的资源列表。
List
通常用于搜索资源, List
适合来自单个集合的数据,该集合的大小有限,而不是高速缓存。 对于更广泛的情况,应使用自定义的Search
方法。
批处理获取(如采用多个资源ID并返回每个ID的对象)的方法应该被实现为自定义BatchGet
方法而不是List
。 但是,如果您已有一个已经存在的List
方法提供相同的功能,则可以重新使用List
方法。 如果您使用自定义BatchGet
方法,则应将其映射到HTTP GET
。
适用的常见模式:分页,结果排序。
适用的命名约定:过滤器字段,结果字段。
HTTP映射:
-
List
方法b必须使用HTTP的GET
动词. -
接收列出其资源的集合的名称的请求消息字段应映射到URL路径。 如果集合名称映射到URL路径,则URL模板的最后一个段(集合ID)必须是字面值。
-
所有剩余的请求消息字段应映射到URL查询参数。
-
没有请求body,API配置不能声明一个body子句。
-
响应body应包含资源列表以及可选元数据。
以下代码示例定义了一个书本集合的标准List
方法 :
// 列出给定的书架上的所有书本。
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
Get
方法将有一个资源名,零或多个参数,和资源的返回值说明。
HTTP mapping:
-
The
Get
方法使用HTTP的Get
形式。 -
The request message field(s) receiving the resource name should map to the URL path.
-
All remaining request message fields shall map to the URL query parameters.
-
There is no request body; the API configuration must not declare a
body
clause. -
The returned resource shall map to the entire response body.
The following code example defines a standard Get
method that gets a specified book:
// Gets the specified 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
Create
方法拥有一组名称,一个资源,零个或多个参数,它在指定的集合中创建一个新资源,并返回新创建的资源。
如果API支持创建资源,则应该为可以创建的每种资源类型创建一个Create
方法。
HTTP映射:
-
Create
方法必须使用HTTPPOST
动词。 -
请求消息应该有一个字段
parent
,指定资源要创建的父资源名称。 -
所有剩余的请求消息字段应映射到URL查询参数。
-
请求可能包含名为
<resource>_id
的字段,以允许呼叫者选择客户端分配的ID。 该字段必须映射到URL查询参数。 -
包含资源的请求消息字段应映射到请求主体。 如果
body
的HTTP配置子句用于Create
方法,则必须使用body:"<resource_field>"
表单。 -
返回的资源将映射到整个响应体。
如果Create
方法支持客户端分配的资源名称并且资源已经存在,则该请求应该失败,并出现错误代码google.rpc.Code.ALREADY_EXISTS
,或使用不同的服务器分配的资源名称,并且文档应清楚创建的资源名称 可能与传入的不同。
以下代码示例定义了一个标准的Create
方法,该方法在父书架内创建一本书:
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 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
方法可变,除了包含资源名称或父属性。 重新命名或移动资源的任何功能不得在Update
方法中发生,而应由自定义方法处理。
HTTP映射:
-
标准
Update
方法应该支持部分资源更新,并使用名为update_mask
的FieldMask
字段的HTTP动词PATCH
。 -
需要使用更高级的修补语义(如附加到重复字段)的
Update
方法应该通过自定义方法提供。 -
如果
Update
方法仅支持完全资源更新,则必须使用HTTP动词PUT
。 但是,完全更新非常不鼓励,因为在添加新的资源字段时它具有向后的兼容性问题。 -
接收资源名称的消息字段必须映射到URL路径。 该字段可能在资源消息本身中。
-
包含资源的请求消息字段必须映射到请求主体。
-
所有剩余的请求消息字段必须映射到URL查询参数。
-
响应消息必须是更新的资源本身。
如果API接受客户端分配的资源名称,则服务器可能允许客户端指定不存在的资源名称并创建新的资源。 否则,Update
方法应该以不存在的资源名称失败。 如果它是唯一的错误条件,则应使用错误代码NOT_FOUND
。
具有支持资源创建的Update
方法的API还应提供Create
方法。 理由是,如果Update
方法是唯一的方法,它不清楚如何创建资源。
以下代码示例定义了更新指定书籍的标准Update
方法:
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
方法使用资源名称和零个或多个参数,并删除或调度以删除指定的资源。 Delete
方法应该返回google.protobuf.Empty
。
API不应该依赖于Delete
方法返回的任何信息,因为它不能重复调用。
HTTP映射:
-
Delete方法必须使用HTTP
DELETE
动词。 -
接收资源名称的请求消息字段应映射到URL路径。
-
所有剩余的请求消息字段应映射到URL查询参数。
-
没有请求机构 API配置不能声明一个
body
子句。 -
如果
Delete
方法立即删除资源,它应该返回一个空的响应。 -
如果
Delete
方法启动长时间运行的操作,则应返回长时间运行的操作。 -
如果
Delete
方法仅将资源标记为已删除,则应返回更新的资源。
Calls to the Delete
method should be idempotent in effect, but do not need to yield the same response. Any number ofDelete
requests should result in a resource being (eventually) deleted, but only the first request should result in a success code. Subsequent requests should result in a google.rpc.Code.NOT_FOUND
.
The following code example defines a standard Delete
method that deletes a specified 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 mapping
对于自定义方法,他们应该使用以下通用HTTP映射:
https://service.name/v1/some/resource/name:customVerb
使用:
而不是/
将资源名称中的自定义动词分开的原因是可以支持任意路径。 例如,取消删除文件可以映射到 POST /files/a/long/file/name:undelete
选择HTTP映射时应适用以下准则:
-
自定义方法应该使用HTTP
POST
动词,因为它具有最灵活的语义。 -
自定义方法不应该使用HTTP
PATCH
,但可能使用其他HTTP动词。在这种情况下,方法必须遵循该动词的标准HTTP语义。 -
值得注意的是,使用HTTP
GET
的自定义方法必须是幂等的,没有副作用。例如,在资源上实现特殊视图的自定义方法应该使用HTTPGET
。 -
接收与自定义方法相关联的资源或集合的资源名称的请求消息字段应映射到URL路径。
-
URL路径必须以后缀为自定义动词的冒号结尾。
-
如果用于自定义方法的HTTP动词允许HTTP请求体(
POST
,PUT
,PATCH
或自定义HTTP动词),则此类自定义方法的HTTP配置必须使用body:"*"
子句和所有其余请求消息字段应映射到HTTP请求体。 -
如果用于自定义方法的HTTP动词不接受HTTP请求体(
GET
,DELETE
),则此类方法的HTTP配置根本不能使用body
子句,所有剩余的请求消息字段都应映射到URL查询参数。
WARNING: If a service implements multiple APIs, the API producer must carefully create the service configuration to avoid custom verb conflicts between APIs.
// 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用户更易于发现的优点,并更直接地模拟概念。
-
激励员工. 如果实现是一个标准更新,客户端将不得不复制管理激励过程的公司政策,以确保激励在相同的职业阶梯中发生正确的水平。
-
批量方法. 对于性能关键方法,提供自定义批处理方法以减少每个请求开销可能是有用的。 例如,
accounts.locations.batchGet
。
标准方法比自定义方法更合适的几个例子:
-
使用不同的查询参数查询资源(使用标准
list
过滤的标准列表方法)。 -
简单的资源属性更改(使用具有字段掩码的标准
update
方法)。 -
关闭通知(使用标准
delete
方法)。
通用的自定义方法
常用或有用的自定义方法名称的策略列表如下。 API设计人员在引入自己的API之前应该考虑这些名称,以便于跨API的一致性。
方法名称 | 自定义谓语 | HTTP 谓语 | 注释 |
---|---|---|---|
Cancel | :cancel |
POST |
取消未完成的操作(构建,计算等)。 |
BatchGet<plural noun> | :batchGet |
GET |
批量获取多个资源。 (详见列表的说明) |
Move | :move |
POST |
将资源从一个父项移动到另一个父项。 |
Search | :search |
GET |
用于获取不符合List语义的数据的List的替代方法。 |
Undelete | :undelete |
POST |
还原以前删除的资源。 推荐的保留期为30天。 |
标准字段
本节介绍在需要类似概念时应使用的一组标准消息字段定义。 这将确保相同的概念在不同的API中具有相同的名称和语义。
名称 | 类型 | 描述 |
---|---|---|
name |
string |
name 字段应包含相对资源名称。 |
parent |
string |
对于资源定义和List /Create 请求,parent 字段应包含父相对资源名称。 |
create_time |
Timestamp |
实体的创建时间戳。 |
update_time |
Timestamp |
实体的最后更新时间戳。 注意:执行create / patch /delete 操作时update_time 被更新。 |
delete_time |
Timestamp |
实体的删除时间戳记,只有当它支持保留时。 |
time_zone |
string |
时区名称。 它应该是IANA TZ名称,例如“America / Los_Angeles”。 有关详细信息,请参阅https://en.wikipedia.org/wiki/List_of_tz_database_time_zones。 |
region_code |
string |
位置的Unicode国家/地区代码(CLDR),例如“US”和“419”。 有关详细信息,请参阅http://www.unicode.org/reports/tr35/#unicode_region_subtag。 |
language_code |
string |
BCP-47语言代码,如“en-US”或“sr-Latn”。 有关详细信息,请参阅http://www.unicode.org/reports/tr35/#Unicode_locale_identifier。 |
display_name |
string |
实体的显示名称。 |
title |
string |
实体的正式名称,如公司名称。 它应该被视为display_name 的正式版本。 |
description |
string |
实体的一个或多个文本描述段落。 |
filter |
string |
List方法的标准过滤器参数。 |
query |
string |
如果应用于搜索方法(即:搜索),则与filter 相同(ie :search ) |
page_token |
string |
列表请求中的分页令牌。 |
page_size |
int32 |
列表请求中的分页大小。 |
total_size |
int32 |
列表中的项目总数,不管分页。 |
next_page_token |
string |
List响应中的下一个分页令牌。 它应该用作page_token 作为以下请求。 一个空值意味着没有更多的结果。 |
order_by |
string |
指定列表请求的结果顺序。 |
request_id |
string |
用于检测重复请求的唯一字符串ID。 |
resume_token |
string |
用于恢复流请求的不透明令牌。 |
labels |
map<string,string> |
表示云资源标签。 |
deleted |
bool |
如果一个资源允许取消删除行为,它必须有一个已deleted 的字段,指示资源被删除。 |
show_deleted |
bool |
如果资源允许取消删除行为,则相应的列表方法必须具有show_deleted 字段,以便客户端可以发现已删除的资源。 |
update_mask |
FieldMask |
它用于Update 请求消息,用于对资源执行部分更新。 |
validate_only |
bool |
如果为true,表示给定的请求只能被验证,而不是执行。 |
错误
本章概述了Google API错误模型以及开发人员如何正确生成和处理错误的一般指导。
Google API使用简单的协议无关错误模型,可让我们在不同的API,API协议(如gRPC或HTTP)以及错误上下文(例如异步,批处理或工作流错误)中公开一致的体验。
错误模型
错误模型由google.rpc.Status
在逻辑上定义,当发生API错误时,它的一个实例返回给客户端。 以下代码片段显示了错误模型的总体设计:
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的开发人员)可以对错误进行响应并进行更正。
-
保留错误消息简短。 如果需要,请提供一个链接,让困惑的读者可以提出问题,提供反馈或获取不完全符合错误信息的更多信息。 否则,使用详细信息字段来展开。
错误详情
Google API为错误详细信息定义了一组标准错误有效载荷,您可以在google/rpc/error_details.proto中找到。这些涵盖了API错误最常见的需求,例如配额失败和无效参数。像错误代码一样,错误细节应尽可能使用这些标准有效载荷。
只有在可以帮助应用程序代码来处理错误的情况下,才应引入附加的错误详细信息类型。如果错误信息只能由人类处理,请依赖于错误消息内容,让开发人员手动处理错误信息,而不是引入新的错误详细信息类型。请注意,如果引入了其他错误详细信息类型,则必须明确注册它们。
以下是一些示例error_details
的有效载荷:
-
RetryInfo
描述当客户端可以重试失败的请求时,可能会在Code.UNAVAILABLE
或Code.ABORTED
上返回 -
QuotaFailure
描述配额检查如何失败,可能会在Code.RESOURCE_EXHAUSTED
上返回 -
BadRequest
描述客户端请求中的违规行为,可以在Code.INVALID_ARGUMENT
上返回
HTTP映射
虽然proto3消息具有本地化的JSON编码,但Google的API平台使用以下JSON表示形式进行直接的HTTP-JSON错误响应,以允许向后兼容性:
{
"error": {
"code": 401,
"message": "Request had invalid credentials.",
"status": "UNAUTHENTICATED",
"details": [{
"@type": "type.googleapis.com/google.rpc.RetryInfo",
...
}]
}
}
字段 | 描述 |
---|---|
error |
额外的层面是为了向后兼容Google API客户端库。 它还使JSON表示对人类更加可读。 |
code |
Status.code 的HTTP状态代码映射 |
message |
对应 Status.message
|
status |
对应 Status.code
|
details |
对应 Status.details
|
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错误代码的表格,以及其原因的简短说明。 要处理错误,您可以检查返回的状态代码的描述,并相应地修改您的呼叫。
HTTP | RPC | 描述 |
---|---|---|
200 | OK |
正常. |
400 | INVALID_ARGUMENT |
客户端指定了无效参数,检查错误信息和错误详细信息以获取更多信息。 |
400 | FAILED_PRECONDITION |
请求无法在当前系统状态下执行,如删除非空目录。 |
400 | OUT_OF_RANGE |
过界,客户端需要指明无效范围。 |
401 | UNAUTHENTICATED |
由于缺失、无效或过期的OAuth token,请求未通过身份验证。 |
403 | PERMISSION_DENIED |
客户端没有足够的权限。这可能是因为OAuth token没有正确的范围,客户端没有权限,还没有为客户端项目启用API。 |
404 | NOT_FOUND |
指定资源没有被发现,或者该请求被未公开的原因拒绝,例如白名单。 |
409 | ABORTED |
并发冲突,如读 - 修改 - 写冲突。 |
409 | ALREADY_EXISTS |
客户端尝试创建的资源已经存在。 |
429 | RESOURCE_EXHAUSTED |
资源配额或者达到限制速率,客户端应该查找google.rpc.QuotaFailure错误详细信息以获取更多信息。 |
499 | CANCELLED |
请求被客户取消。 |
500 | DATA_LOSS |
不可恢复的数据丢失或数据损坏。 客户端应该向用户报告错误。 |
500 | UNKNOWN |
未知的服务器错误。 通常是服务器错误。 |
500 | INTERNAL |
内部服务器错误。 通常是服务器错误。 |
501 | NOT_IMPLEMENTED |
API方法未由服务器实现。 |
503 | UNAVAILABLE |
服务不可用,一般是服务器宕机所致。 |
504 | DEADLINE_EXCEEDED |
请求超期,如果重复发生,请考虑减少请求的复杂性。 |
错误重试
客户端应该重新尝试500、503和504的错误,并使用指数级的恢复。 最小延迟应为1秒,除非另有说明。对于429错误,客户端可能会重试至少30秒的延迟。 对于所有其他错误,重试可能不适用 - 首先确保您的请求是幂等的,并查看错误消息以获得指导。
错误传播
如果您的API服务依赖于其他服务,则不应盲目地将这些服务中的错误传播给您的客户。 翻译错误时,我们建议如下:
-
隐藏实施细节和机密信息。
-
调整对错误负责的一方。 例如,从其他服务接收INVALID_ARGUMENT错误的服务器应将INTERNAL传播到自己的调用者。
如何生成错误提示
如果您是服务器开发人员,您应该产生足够的信息来帮助客户开发人员了解并解决问题。 同时,您必须了解用户数据的安全性和隐私性,并避免在错误消息和错误详细信息中披露敏感信息,因为错误经常被记录并可能被其他人访问。 例如,诸如“客户端IP地址不在白名单128.0.0.0/8”之类的错误消息暴露了有关服务器端策略的信息,用户可能无法访问这些信息。
为了产生正确的错误,您首先需要熟悉google.rpc.Code,为每个错误条件选择最合适的错误代码。 服务器应用程序可以并行检查多个错误条件,并返回第一个。
下表列出了每个错误代码和一个良好的错误消息的示例。
HTTP | RPC | Example Error Message |
---|---|---|
400 | INVALID_ARGUMENT |
Request field x.y.z is xxx, expected one of [yyy, zzz]. |
400 | FAILED_PRECONDITION |
Resource xxx is a non-empty directory, so it cannot be deleted. |
400 | OUT_OF_RANGE |
Parameter 'age' is out of range [0, 125]. |
401 | UNAUTHENTICATED |
Invalid authentication credentials. |
403 | PERMISSION_DENIED |
Permission 'xxx' denied on file 'yyy'. |
404 | NOT_FOUND |
Resource 'xxx' not found. |
409 | ABORTED |
Couldn’t acquire lock on resource ‘xxx’. |
409 | ALREADY_EXISTS |
Resource 'xxx' already exists. |
429 | RESOURCE_EXHAUSTED |
Quota limit 'xxx' exceeded. |
499 | CANCELLED |
Request cancelled by the client. |
500 | DATA_LOSS |
See note. |
500 | UNKNOWN |
See note. |
500 | INTERNAL |
See note. |
501 | NOT_IMPLEMENTED |
Method 'xxx' not implemented. |
503 | UNAVAILABLE |
See note. |
504 | DEADLINE_EXCEEDED |
See note. |
NOTE:由于客户端无法修复服务器错误,因此无法生成其他错误详细信息。 为了避免在错误条件下泄漏敏感信息,建议不要生成任何错误消息,只生成google.rpc.DebugInfo
错误详细信息。 DebugInfo
是专门为服务器端日志记录而设计的,不能发送给客户端。
google.rpc
软件包定义了一组标准错误有效载荷,它们优先于自定义错误有效载荷。 下表列出了每个错误代码及其匹配的标准错误有效载荷(如果适用)。
HTTP | RPC | Recommended Error Detail |
---|---|---|
400 | INVALID_ARGUMENT |
google.rpc.BadRequest |
400 | FAILED_PRECONDITION |
google.rpc.PreconditionFailure |
400 | OUT_OF_RANGE |
google.rpc.BadRequest |
401 | UNAUTHENTICATED |
|
403 | PERMISSION_DENIED |
|
404 | NOT_FOUND |
google.rpc.ResourceInfo |
409 | ABORTED |
|
409 | ALREADY_EXISTS |
google.rpc.ResourceInfo |
429 | RESOURCE_EXHAUSTED |
google.rpc.QuotaFailure |
499 | CANCELLED |
|
500 | DATA_LOSS |
|
500 | UNKNOWN |
|
500 | INTERNAL |
|
501 | NOT_IMPLEMENTED |
|
503 | UNAVAILABLE |
|
504 | DEADLINE_EXCEEDED |
命名约定
为了在许多API和长时间内提供一致的开发人员体验,API使用的所有名称应为:
-
简单
-
直觉
-
一致
这包括接口,资源,集合,方法和消息的名称。
由于许多开发人员不是英文母语人士,因此这些命名约定的目标之一是确保大多数开发人员能够轻松了解API。 它通过鼓励在命名方法和资源时使用简单,一致和小的词汇表来实现。
-
API中使用的名称应该是正确的美国英语。例如,license(而不是licence),color(而不是colour)。
-
可以简单地使用常用的简短形式或长字的缩写。例如,API优于应用程序编程接口。
-
尽可能使用直观,熟悉的术语。例如,当描述删除(和销毁)资源时,删除是优先于擦除。
-
对同一概念使用相同的名称或术语,包括跨API共享的概念。
-
避免名称重载。为不同的概念使用不同的名称。
-
避免在API和Google API的更大生态系统的上下文中含有不明确的通用名称。他们可能会导致对API概念的误解。相反,请选择准确描述API概念的特定名称。这对于定义一阶API元素(例如资源)的名称尤其重要。没有明确的名称列表来避免,因为每个名字都必须在其他名称的上下文中进行评估。实例,信息和服务是过去有问题的名称的示例。所选择的名称应该清楚地描述API概念(例如:什么的实例),并将其与其他相关概念区分开(例如:“alert”是指规则,信号还是通知?)。
-
仔细考虑使用可能与常用编程语言中的关键字冲突的名称。可以使用这些名称,但在API审查期间可能会触发额外的审查。谨慎和谨慎地使用它们。
产品名称
产品名称指的是API的产品营销名称,如Google Calendar API。 API,UI,文档,服务条款,结算单,商业合同等一贯使用产品名称。
Google API必须使用从Google开始的产品名称,除非它们以不同的品牌提供,例如Gmail,Nest,YouTube。 一般来说,产品名称应由产品和营销团队决定。
下表显示了所有相关API名称及其一致性的示例。 有关各自名称及其约定的更多详细信息,请参阅本页面。
API Name | Example |
---|---|
Product Name | Google Calendar API |
Service Name | calendar.googleapis.com |
Package Name | google.calendar.v3 |
Interface Name | google.calendar.v3.CalendarService |
Source Directory | //google/calendar/v3 |
API Name | calendar |
服务名称
服务名称应该是可以解析为一个或多个网络地址的语法有效的DNS名称(根据RFC 1035)。 公开Google API的服务名称遵循以下格式:xxx.googleapis.com
。 例如,Google日历的服务名称是calendar.googleapis.com
。
如果一个API由几个服务组成,那么它们应该以一种帮助可发现性的方式命名。 一种方法是使服务名称共享公共前缀。 例如,服务build.googleapis.com
和buildresults.googleapis.com
都是Google Build API的一部分。
包名称
在API .proto
文件中声明的软件包名称应与产品和服务名称一致。 具有版本的API的软件包名称必须以版本结尾。 例如:
// Google Calendar API
package google.calendar.v3;
与Google Watcher API等服务无直接关联的抽象API应使用与产品名称一致的原包名称:
// Google Watcher API
package google.watcher.v1;
API .proto
文件中指定的Java程序包名称必须与标准Java程序包名称前缀(com.
,edu.
,net.
等)的原始程序包名称相匹配。 例如:
package google.calendar.v3;
// Specifies Java package name, using the standard prefix "com."
option java_package = "com.google.calendar.v3";
集合IDs
集合ID应使用复数形式和lowerCamelCase
,以及美式英文拼写和语义。 例如:events
,children
或deletedEvents
。
接口命名
为了避免与服务名称(如pubsub.googleapis.com
)混淆,术语接口名称是指在.proto
文件中定义service
时使用的名称:
// Library is the interface name.
service Library {
rpc ListBooks(...) returns (...);
rpc ...
}
您可以将服务名称视为对一组API的实际实现的参考,而接口则引用API的抽象定义。
接口名称应该使用直观的名词,如Calendar或Blob。 该名称不应与编程语言及其运行时库(例如File)中任何已建立的概念冲突。
在少见的情况下,接口名会与API中的另一个名称冲突,应使用后缀(例如Api
或Service
)来消除歧义。
方法命名
在IDL规范中,服务可以定义一个或多个与集合和资源上的方法相对应的RPC方法。方法名应该遵循上驼峰上的VerbNoun的命名惯例,在这个例子中,名词通常是资源类型。
名词 | 方法名称 | 请求消息 | 响应消息 | |
---|---|---|---|---|
List |
Book |
ListBooks |
ListBooksRequest |
ListBooksResponse |
Get |
Book |
GetBook |
GetBookRequest |
Book |
Create |
Book |
CreateBook |
CreateBookRequest |
Book |
Update |
Book |
UpdateBook |
UpdateBookRequest |
Book |
Rename |
Book |
RenameBook |
RenameBookRequest |
RenameBookResponse |
Delete |
Book |
DeleteBook |
DeleteBookRequest |
google.protobuf.Empty |
方法名的动词部分应使用命令式语气,即用于要求或命令,而不是用于提问的指示性语气。
当动词提出关于API中的子资源的问题时,这很容易混淆,这通常以指示性语气表达。 例如,订购API来创建一本书,这显然是CreateBook
(强制性语气),但是要求API关于图书发行者的状态可能会使用指示性的语气,例如IsBookPublisherApproved
或NeedsPublisherApproval
。 要在这样的情况下保持紧张的语气,依靠诸如“check”(CheckBookPublisherApproved
)和“validate”(ValidateBookPublisher
)等命令。
消息命名
RPC方法的请求和响应消息应分别以后缀为Request和Response的方法名命名,除非方法请求或响应类型为:
-
一个空信息(使用
google.protobuf.Empty
), -
资源类型,或
-
表示操作的资源
这通常适用于在标准方法Get
, Create
, Update
, 或 Delete
中使用的请求或响应。
枚举命名
枚举类型必须使用UpperCamelCase名称。
枚举值必须使用CAPITALIZED_NAMES_WITH_UNDERSCORES。 每个枚举值必须以分号结尾,而不能以逗号结尾。 第一个值应该命名为ENUM_TYPE_UNSPECIFIED,因为当枚举值未被明确指定时返回。
enum FooBar {
// The first value represents the default and must be == 0.
FOO_BAR_UNSPECIFIED = 0;
FIRST_VALUE = 1;
SECOND_VALUE = 2;
}
字段命名
.proto
文件中的字段定义必须使用lower_case_underscore_separated_names
。 这些名称将映射到每种编程语言的生成代码中的本机命名约定。
字段名称应避免介词(例如“for”,“during”,“at”),例如:
-
reason_for_error
应该是error_reason
-
cpu_usage_at_time_of_failure
应该是fail_time_cpu_usage
字段名称也应避免使用后置正式形容词(名词之后的修饰符),例如:
-
items_collected
应该是collect_items
-
objects_imported
应该是import_objects
复数字段的命名
API中的复数字段必须使用适当的复数形式。 这符合现有Google API的惯例,也是外部开发人员的常用惯例。
时间和时间段
为了表示一个时间点独立于任何时区或日历,应该使用google.protobuf.Timestamp
,并且字段名称应以time
结尾,如start_time
和end_time
。
如果时间是指一个活动,则字段名称应该具有verb_time的形式,如create_time
,update_time
。 避免使用过去时的动词,如created_time
或last_updated_time
。
代表两个时间点之间的时间间隔,与时间无关,如“日”或“月”,应该使用google.protobuf.Duration
等概念。
message FlightRecord {
google.protobuf.Timestamp takeoff_time = 1;
google.protobuf.Duration flight_duration = 2;
}
如果由于遗留或兼容性原因(包括挂钟时间,持续时间,推迟和延迟),必须使用整数类型表示与时间相关的字段,则字段名称必须具有以下格式:
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年7月30日。
对于独立于时区和日期的时间段,应使用google.type.TimeOfDay
,并应使用后缀_time
。 如果一天中的时间必须以字符串形式表示,那么它应该是ISO 8601 24小时制格式HH:MM:SS [.FFF],例如。14:55:01.672。
message StoreOpening {
google.type.Date opening_date = 1;
google.type.TimeOfDay opening_time = 2;
}
数量表示
以整数类型表示的数量必须包括测量单位。
xxx_{bytes|width_pixels|meters}
如果数量是许多项,则该字段应该具有后缀_count
,例如node_count
。
列表过滤字段
如果API支持对List
方法返回的资源进行过滤,则包含过滤器表达式的字段应该命名为filter
。 例如:
message ListBooksRequest {
// The parent resource name.
string parent = 1;
// The filter expression.
string filter = 2;
}
List的响应
列表方法的响应消息中包含资源 List
的字段名称必须是资源名称本身的复数形式。 例如,一个方法CalendarApi.ListEvents()
必须定义一个响应消息ListEventsResponse
,其中一个称为events
的重复字段用于返回的资源列表。
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 Style
定义的大写的驼峰写法名称。
名称缩写
对于软件开发人员(如配置和规范)中众所周知的缩写,缩写应用于API定义,而不是完整的拼写。 这将使源代码易于阅读和写入。 在正式文件中,应使用完整的拼写。 例子:
-
配置(配置)
-
id(标识符)
-
规格(规格)
-
统计(统计)
通用设计模式
空响应
标准的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>
标签。
message Book {
string name = 1;
map<string, string> labels = 2;
}
长运行操作
如果API方法通常需要很长时间才能完成,则可以设计为将长运行操作资源返回给客户端,客户端可以使用该资源来跟踪进度并接收结果。该操作定义了一个标准接口来处理长时间运行的操作。单个API不能为长时间运行的操作定义自己的接口,以避免不一致。
操作资源必须作为响应消息直接返回,并且操作的任何直接后果应反映在API中。例如,创建资源时,该资源应该出现在LIST和GET方法中,尽管资源应该指示它尚未准备好使用。当操作完成时,如果方法不长时间运行,Operation.response
字段应包含直接返回的消息。
操作可以使用Operation.metadata
字段提供有关其进度的信息。即使初始实现不填充metadata
字段,API也应该定义该元数据的消息。
列表分页
列表形式的集合应该支持分页,即使结果通常很小。
理由:尽管添加了对现有API的分页支持纯粹是从API表面的角度来添加的,但这是一种使用方式上的改变,不知道已经转变为分页形式的已有客户端将错误地假设它们收到了完整的列表结果,而它们只接收第一个页面。
为了在List
方法中支持分页(返回列表结果页),API应该遵循以下原则:
-
在
List
方法的请求消息中定义一个page_token
字段。客户端使用此字段请求列表结果的指定页面。 -
在
List
方法的请求消息中定义一个int32
字段page_size
。客户端使用此字段指定要由服务器返回的最大结果数。服务器可以进一步约束在单个页面中返回的最大结果数量。如果page_size
为0
,则服务器将决定要返回的结果数。 -
在
List
方法的响应消息中定义一个字符串字段next_page_token
。此字段表示检索下一页结果的分页令牌。如果值为""
,则表示该请求没有进一步的结果。
要检索下一页结果,客户端应在随后的List
方法调用(请求消息的page_token
字段)中传递响应的next_page_token
的值:
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);
message ListBooksRequest {
string name = 1;
int32 page_size = 2;
string page_token = 3;
}
message ListBooksResponse {
repeated Book books = 1;
string next_page_token = 2;
}
当客户端传递除了页面token之外查询参数时,如果查询参数与页面token不一致,则服务必须对请求失败。
页面token内容应该是一个网络安全的base64编码协议
buffer。 这允许内容发展而没有兼容性问题。 如果页面token包含潜在的敏感信息,则该信息应该被加密。 服务必须防止篡改页面token通过以下方法之一暴露无意的数据:
-
需要根据后续请求分配查询参数。
-
仅在页面token中引用服务器端会话状态。
-
对页面token中的查询参数进行加密和签名,并在每次调用时重新验证和重新授权这些参数。
分页的实现还可以提供名为total_size
的int32
字段中的项目总数。
列表子集合
有时,一个API需要让一个客户端List/Search
跨子集。 例如,图书馆API有一个书架集合,每个书架都有一组书籍,客户想要在所有货架上搜索一本书。 在这种情况下,建议在子集合中使用标准List
,并为父集合指定通配符集合"-"
。 对于图书馆API示例,我们可以使用以下REST API请求:
GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx
NOTE: 选择"-"
而不是"*"
的原因是为了避免URL转义的需要。
从子集获取唯一资源
有时,子集合中的资源具有在其父集合内是唯一的标识符。 在这种情况下,允许Get
在不知道哪个父集合包含它的情况下检索该资源可能是有用的。 在这种情况下,建议使用资源上的标准Get
,并为资源唯一的所有父集合指定通配符集合"-"
。 例如,在图书馆API中,我们可以使用以下REST API请求,如果该书在所有书架上的所有书籍中都是唯一的:
GET https://library.googleapis.com/v1/shelves/-/books/{id}
对此调用的响应中的资源名称必须使用资源的规范名称,每个父集合使用实际的父集合标识符而不是"-"
。 例如,上面的请求应该返回一个名为shelves/shelf713/books/book8141
,而不是shelves/-/books/book8141
的资源。
排序
如果API方法允许客户端为列表结果指定排序顺序,请求消息应包含一个字段:
string order_by = ...;
字符串值应遵循SQL语法:逗号分隔的字段列表。 例如:"foo,bar"
。 默认排序顺序为升序。 要指定字段的降序,后缀"desc"
应附加到字段名称。 例如:"foo desc,bar"
。
冗余的空格字符在语法上是微不足道的。 "foo,bar desc"
和"foo,bar desc"
相当。
请求验证
如果API方法有副作用,并且需要验证请求而不引起这种副作用,请求消息应包含一个字段:
bool validate_only = ...;
如果这个字段被设置为true,服务器就不能执行任何副作用,并且只执行与完整请求一致的特定于实现的验证。
如果验证成功,则必须返回google.rpc.Code.OK
,并且使用相同请求消息的任何完整请求不应返回google.rpc.Code.INVALID_ARGUMENT
。 请注意,由于其他错误(如google.rpc.Code.ALREADY_EXISTS
)或由于竞争条件,请求可能仍然失败。
请求幂等
对于网络API,特权API方法是非常优先的,因为它们可以在网络故障后安全地重试。 然而,一些API方法不能轻易地是幂等的,例如创建资源,并且需要避免不必要的重复。 对于这种用例,请求消息应包含一个唯一的ID,如UUID,服务器将使用它来检测重复,并确保只处理该请求一次。
// 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
。
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
是Resource View
枚举中的'0'
值。
语法
在某些API设计中,有必要为某些数据格式定义简单的语法,例如可接受的文本输入。 为了在API之间提供一致的开发人员体验并减少学习曲线,API设计人员必须使用ISO 14977 Extended Backus-Naur Form(EBNF)语法来定义此类语法:
Production = name "=" [ Expression ] ";" ;
Expression = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term = name | TOKEN | Group | Option | Repetition ;
Group = "(" Expression ")" ;
Option = "[" Expression "]" ;
Repetition = "{" Expression "}" ;
NOTE: TOKEN
表示在语法之外定义的终端。
整形类型
在API设计中,不应使用无符号整数类型,例如uint32
和fixed32
,因为一些重要的编程语言和系统不能很好地支持它们,例如Java,JavaScript和OpenAPI。 而且它们更有可能导致溢出错误。 另一个问题是,不同的API很可能对同一件事情使用不匹配的有符号和无符号类型。
当带符号的整数类型用于负值不重要的事物(如大小或超时)时,值-1
(且仅为-1
)可用于指示特殊含义,例如文件结尾(EOF),无限大 超时,无限制配额限制或未知年龄。 必须清楚记录这些用法以避免混淆。 如果API生成器不是非常明显的话,API生成器还应记录隐式默认值0
的行为。
子集响应
有时API客户端只需要响应消息中的特定数据子集。 为了支持这种用例,一些API平台为部分响应提供本地支持。 Google API Platform通过响应字段掩码支持它。 对于任何REST API调用,都有一个隐式的系统查询参数$ fields
,它是一个google.protobuf.FieldMask
值的JSON表示形式。 响应消息将被发送回客户端之前被$fields
过滤。 API平台为所有API方法自动处理此逻辑。
GET https://library.googleapis.com/v1/shelves?$fields=name
资源视图
为了减少网络流量,允许客户端限制服务器在其响应中返回的资源的哪些部分是有用的,返回资源的视图,而不是完整的资源表示。 通过向方法请求中添加参数来实现API中的资源视图支持,该参数允许客户端在响应中指定要接收的资源的哪个视图。
参数:
-
应该是枚举类型
-
必须命名视图
枚举的每个值定义资源的哪些部分(哪些字段)将在服务器的响应中返回。 正是为每个视图值返回的值是实现定义的,应在API文档中指定。
package google.example.library.v1;
service Library {
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
option (google.api.http) = {
get: "/v1/{name=shelves/*}/books"
}
};
}
enum BookView {
// 服务器响应只包括作者,标题,ISBN和独特的书籍ID。
// The default value.
BASIC = 0;
// 本书的完整表示在服务器响应中返回,
// 包括书的内容。
FULL = 1;
}
message ListBooksRequest {
string name = 1;
// 指定应该返回图书资源的哪些部分
BookView view = 2;
}
This construct will be mapped to URLs such as:
GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC
You can find out more about defining methods, requests, and responses in the Standard Methods chapter of this Design Guide.
ETags
ETag是一个不透明的标识符,允许客户端进行条件请求。 为了支持ETag,API应在资源定义中包含一个字符串字段etag
,其语义必须与ETag的常用用法相匹配。 通常,etag
包含由服务器计算的资源的指纹。 有关详细信息,请参阅维基百科和RFC 7232。
ETags可以强或弱验证,其中弱验证的ETag以W /为前缀。 在这种情况下,强验证意味着具有相同ETag的两个资源具有字节/字节相同的内容和相同的额外字段(即Content-Type)。 这意味着强烈验证的ETag允许缓存部分响应,以便稍后进行组合。
相反,具有相同弱验证的ETag值的资源意味着表示在语义上是等效的,但不一定是字节的字节相同,因此不适合于字节范围请求的响应缓存。
例如:
// This is a strong ETag, including the quotes.
"1a2f3e4d5b6c7c"
// This is a weak ETag, including the prefix and quotes.
W/"1a2b3c4d5ef"
输出字段
API可能希望区分由客户端提供的字段作为输入和仅在服务器上在特定资源的输出上返回的字段。 对于仅输出的字段,必须记录字段属性。
请注意,如果客户端在请求中设置了仅输出字段,或者客户端指定了仅输出域的google.protobuf.FieldMask
,则服务器必须接受请求而不会出错。 这意味着服务器必须忽略仅输出字段的存在及其任何指示。 这个建议的原因是因为客户端通常会将服务器返回的资源重用为另一个请求输入,例如 一个检索到的书将在UPDATE方法中被再次使用。 如果仅输出字段验证,那么这将在客户端添加额外的工作以清除仅输出字段。
message Book {
string name = 1;
// Output only.
Timestamp create_time = 2;
}
文档
本节提供了将内联文档添加到API的准则。 大多数API还将具有概述,教程和更高级别的参考文档,这些文档超出了本“设计指南”的范围。 有关API,资源和方法命名的信息,请参阅命名约定。
评论格式
Add comments to your .proto
file using the usual Protocol Buffers //
comment format.
// 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。 如果两个文件中都记录了相同的元素,则该文件中的文档将优先于.proto
中的文档。
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描述添加更详细的概述。 但是,一般来说,添加文档注释到.proto
是首选的。
与.proto
注释一样,您可以使用Markdown在YAML文件注释中提供其他格式。
API描述
API描述是一个从活动动词开始的短语,用于描述您可以使用API做什么。 在.proto
文件中,API描述作为注释添加到相应的服务中,如下例所示:
// Manages books and shelves in a simple digital library.
service LibraryService {
...
}
以下是一些更为示例的API说明:
-
与世界各地的朋友分享更新,照片,视频等。
-
访问云托管的机器学习服务,可以轻松构建响应数据流的智能应用程序。
资源描述
资源描述是描述资源代表什么的部分句子。 如果您需要添加更多的细节,请使用其他句子。 在.proto
文件中,将资源描述作为注释添加到相应的消息类型中,如以下示例所示:
// A book resource in the Library API.
message Book {
}
以下是一些示例资源描述:
-
用户的待办事项列表中的任务。 每个任务都有一个独特的优先级。
-
用户日历上的事件。
现场和参数说明
描述字段或参数定义的名词短语,如以下示例所示:
-
本系列中的主题数量。
-
纬度和经度坐标的精确度,以米为单位。必须是非负数。
-
管理是否为本系列中的提交资源返回附件URL值的标志。
serial.insert
的默认值为true
。 -
投票信息的容器。仅当投票信息被记录时才存在。
-
目前尚未使用或已弃用。
现场和参数说明
-
必须明确描述边界(即清楚有效和什么是无效的)请记住,工程师将尽力破坏任何服务,并且无法读取底层代码来澄清任何不清楚的信息。)
-
必须指定任何默认值或默认行为;换句话说,如果没有提供值,服务器将会做什么。
-
如果它是字符串,例如名称或路径,则描述语法和列出允许的字符以及任何所需的编码。例如:
-
集合中的1-255个字符[A-a0-9]
-
从RFC 2332约定开始的有效URL路径字符串。最大长度为500个字符。
-
应尽可能提供示例值。
-
如果需要字段值,仅输入,仅输出,则必须在字段描述开头记录。默认情况下,所有字段和参数都是可选的。例如:
message Table {
// Required. The resource name of the table.
string name = 1;
// Input only. Whether to dry run the table creation.
bool dryrun = 2;
// Output only. The timestamp when the table was created. Assigned by
// the server.
Timestamp create_time = 3;
// The display name of the table.
string display_name = 4;
}
方法描述
一个方法描述是一个句子,指出方法有什么影响以及它所运行的资源。 它通常以第三人称现在时态动词(即以“s结尾的动词”)开始。 如果您需要添加更多的细节,请使用其他句子。 这里有些例子:
-
列出已验证用户的日历事件。
-
使用请求中包含的数据更新日历事件。
-
从经过身份验证的用户的位置记录中删除位置记录。
-
使用请求中包含的数据创建或更新已验证用户的位置记录中的位置记录。 如果位置资源已经存在相同的时间戳值,则提供的数据将覆盖现有数据。
API描述的大纲
确保每个描述简单但完整,并且没有关于API的其他信息的用户可以理解。在大多数情况下,还有更多的是要重申明显的;例如,serial.insert
方法的描述不应该只是说“插入一个系列”。 - 虽然你的命名应该是信息丰富的,但大多数读者都在阅读你的描述,因为他们需要比名字本身提供更多的信息。如果您不清楚说明中还有什么要说的话,请尝试回答以下所有相关问题:
-
它是什么?
-
如果成功,该怎么办?如果失败,该怎么办?什么会导致失败,怎么办?
-
这是幂等吗?
-
什么是单位? (示例:米,度,像素)
-
它接受哪些价值观?范围包括还是排他?
-
有什么副作用?
-
你如何使用它?
-
常见的错误是什么?
-
它总是存在吗? (例如:“投票信息的容器”,只有在记录投票信息时才存在。)
-
它是否有默认设置?
约定
本节列出了文本描述和文档的一些用法约定。 例如,在谈论标识符时使用“ID”(全部大写),而不是“Id”或“id”。 在引用该数据格式时使用“JSON”而不是“Json”或“json”。 以代码字体
显示所有字段/参数名称。 将文字字符串值放在代码字体
和引号中。
-
ID
-
JSON
-
RPC
-
REST
-
property_name
or"string_literal"
-
true
/false
语言风格
在我们的命名约定中,我们建议在编写文档注释时使用简单,一致的词汇和风格。 那些不会说英语的读者应该可以理解这个意见,所以要避免行话,俚语,复杂的隐喻,流行文化的引用或其他不容易理解的内容。 使用友好,专业的风格,直接与开发人员阅读您的评论,并尽可能简明扼要。 请记住,大多数读者想要了解如何使用API做某事,不要阅读文档!
Protocol Buffers v3
本章将讨论如何使用协议缓冲区与API设计。为了简化开发人员的体验并提高运行时效率,gRPC API应使用协议缓冲区版本3(proto3)进行API定义。
协议缓冲区是一种用于定义数据结构模式和编程接口的简单的语言中立和平台中立的界面定义语言(IDL)。它支持二进制和文本线格式,并且可以在不同平台上使用许多不同的线路协议。
Proto3是Protocol Buffers的最新版本,并且包括proto2
的以下更改:
-
现场存在,也称为hasField,不适用于原始字段。未定义的原始字段具有语言定义的默认值。消息字段的存在仍然可用,可以使用编译器生成的
hasFieldmethod
或与null进行比较或由实现定义的sentinel
值进行测试。 -
字段的用户定义的默认值不再可用。
-
枚举定义必须以枚举值零开始。
-
必填字段不再可用。
-
扩展程序已不再可用。使用
google.protobuf.Any
代替。由于后向和运行时兼容性的原因,专门针对google/protobuf/descriptor.proto
授予异常。 -
删除组语法。
删除这些功能的原因是使API设计更简单,更稳定,性能更高。例如,在记录消息之前,通常需要过滤某些字段,例如删除敏感信息。如果这些字段是必需的,这是不可能的。
有关详细信息,请参阅协议缓冲区。
版本控制
本章提供了联网API的版本控制指南。由于一个API服务可以提供多个API接口,所以API版本控制策略适用于API接口级别,而不是在API服务级别。为了方便起见,术语API指的是以下部分中的API接口。
网络API应该使用语义版本控制。给定版本号MAJOR.MINOR.PATCH
,增加:
-
MAJOR
版本当您进行不兼容的API更改时, -
当您以向后兼容的方式添加功能时,
MINOR
版本, -
当您制作向后兼容的错误修复时,
PATCH
版本。
不同的规则适用于根据API版本指定主版本号:
-
对于API的版本1(v1),其主要版本号应以原包名称编码,例如
google.pubsub.v1
。如果软件包包含稳定的类型和不希望有突发性更改的接口,例如google.protobuf
和google.longrunning
,则主包可能会从主包中省略。 -
对于除v1以外的所有版本的API,主版本号必须以原包名称编码。例如,
google.pubsub.v2
。
对于前GA版本(如alpha和beta),建议在版本号后附加一个后缀。后缀应包含预发行版本名称(例如alpha,beta)和可选的预发行版本号。
版本进度示例:
Version | Proto Package | Description |
---|---|---|
v1alpha | v1alpha1 | The v1 alpha release. |
v1beta1 | v1beta1 | The v1 beta 1 release. |
v1beta2 | v1beta2 | The second beta release of v1. |
v1test | v1test | An internal test release with dummy data. |
v1 | v1 | The v1 major version, general availability. |
v1.1beta1 | v1p1beta1 | The first beta release for minor changes to v1. |
v1.1 | v1 | The minor update to v1.1 release. |
v2beta1 | v2beta1 | The v2 beta 1 release. |
v2 | v2 | The v2 major version, general availability. |
次要和补丁编号应反映在API配置和文档中,它们不能以原包名称编码。
NOTE: Google API Platform本来不支持小版本和补丁版本。对于每个主要的API版本,只有一组文档和客户端库。 API所有者需要通过API文档和发行说明手动记录它们。
API的新主要版本不能取决于同一个API的以前的主要版本。 API可能依赖于其他API,以了解与这些API相关联的依赖性和稳定性风险。稳定的API版本必须仅依赖于其他API的最新稳定版本。
在一段时间内,同一API的不同版本必须能够在单个客户端应用程序中同时工作。这是为了帮助客户顺利地从旧版本过渡到较新版本的API。
较旧的API版本应在其弃用期结束后才能删除。
许多API共享的常见且稳定的数据类型(如日期和时间)应在单独的原包中定义。如果有必要进行突破性更改,则必须引入新的类型名称或包含新主版本的软件包名称。
向后兼容
确定什么被视为向后兼容的变化可能是困难的。
以下列表是快速参考起始点,但如果您有任何疑问,请参阅专用的兼容性部分了解更多详细信息。
向后兼容(不间断)更改
-
向API服务添加API接口
-
将方法添加到API接口
-
向方法添加HTTP绑定
-
将字段添加到请求消息
-
将字段添加到响应消息
-
将值添加到枚举
-
添加仅输出资源字段
向后不兼容(断开)更改
-
删除或重命名服务,接口,字段,方法或枚举值
-
更改HTTP绑定
-
更改字段的类型
-
更改资源名称格式
-
更改现有请求的可见行为
-
更改HTTP定义中的URL格式
-
向资源消息添加读/写字段Compatibility
此页面提供了有关Versioningsection
中给出的破坏和不间断更改列表的更详细说明。
这并不总是完全清楚什么是破坏(不相容)的变化。这里的指导应被视为指示性的,而不是每一个可能的变化的综合清单。
这里列出的规则只涉及客户兼容性。预期API生产者在部署方面了解自己的要求,包括实施细节的变化。
一般的目标是客户端不应该被更新到新的小版本或补丁的服务中断。正在考虑的种种破坏是:
-
源兼容性:针对1.0编写的代码无法针对1.1进行编译
-
二进制兼容性:针对1.0无法针对1.1客户端库进行链接/运行的代码编译。 (精确细节取决于客户端平台;在不同情况下有不同的变体)
-
电线兼容性:针对1.0无法与1.1服务器进行通信的应用程序
-
语义兼容性:一切都运行,但产生意想不到或令人惊讶的结果
换句话说:老客户应该可以在同一个主要版本号的较新的服务器上工作,当他们想要更新到一个新的小版本(例如,利用一个新功能)时,他们应该能够这么容易
除了基于协议的理论考虑之外,由于存在涉及生成的代码和手写代码的客户端库,所以存在实际的考虑。通过生成新版本的客户端库并确保其测试仍然通过,尽可能地测试您正在考虑的更改。
下面的讨论将原始消息分为三类:
-
请求消息(如
GetBookRequest
) -
响应消息(如
ListBooksResponse
) -
资源消息(例如
Book
,并包括在其他资源消息中使用的任何消息)
这些类别具有不同的规则,因为请求消息仅从客户端发送到服务器,响应消息仅从服务器发送到客户端,但通常资源消息都以两种方式发送。特别地,可以更新的资源需要根据读取/修改/写入周期来考虑。
向后兼容(不间断)更改
向API服务定义添加API接口
从协议的角度来看,这是永远安全的。 唯一需要注意的是,客户端库可能已经在手写代码中使用了您的新API接口名称。 如果您的新界面与现有界面完全正交,则不太可能; 如果它是现有界面的简化版本,那么更有可能导致冲突。
向API接口添加方法
除非您添加与客户端库中已经生成的方法冲突的方法,否则这应该是正常的。
(可能会破坏的例子):如果您有GetFoo方法,则C#代码生成器将已经创建了GetFoo和GetFooAsync方法。因此,在API接口中添加GetFooAsync方法将是从客户端库角度出发的一个突破性变化。)
向方法添加HTTP绑定
假设绑定没有引入任何歧义,使得服务器响应以前将拒绝的URL是安全的。当将现有操作应用于新的资源名称模式时,可以这样做。
向请求消息添加字段
添加请求字段可以是不间断的,只要不指定该字段的客户端将在新版本中与旧版本相同。
最为明显的例子就是分页:如果API的v1.0不包含分页,则不能添加到v1.1中,除非默认的page_size被视为无限(通常是一个坏主意)。否则,希望从单个请求获得完整结果的v1.0客户端可能会收到截断的结果,而不知道该集合包含更多资源。
向响应消息添加一个字段
只要不改变其他响应字段的行为,就可以扩展不是资源的响应消息(例如ListBooksResponse),而不会破坏客户端。以前在响应中填充的任何字段应该继续使用相同的语义填充,即使这引入了冗余。
例如,1.0中的查询响应可能具有contains_duplicate的布尔字段,以指示由于重复而省略了一些结果。在1.1中,我们可能会在duplicate_count字段中提供更详细的信息。即使从1.1角度来看它是多余的,但是containsduplicates字段仍然必须填充。
向枚举中添加一个值
仅在请求消息中使用的枚举可以自由扩展,以包含新元素。例如,使用资源视图模式,可以在新的次要版本中添加新视图。客户端从来不需要收到这个枚举,所以他们不需要知道他们不在乎的价值观。
对于资源消息和响应消息,默认假设是客户端应该处理它们不知道的枚举值。然而,API生产者应该意识到,编写应用程序来正确处理新的枚举元素可能很困难。 API所有者应该在遇到未知的枚举值时记录预期的客户端行为。
Proto3允许客户端收到他们不知道的值并保留消息维持相同的值,所以这不会中断读取/修改/写入周期。 JSON格式允许在值“未知”的位置发送数字值,但服务器通常不会知道客户端是否真正了解特定值。因此,JSON客户端可能会意识到他们已经收到以前对他们未知的值,但他们只会看到名称或号码 - 他们不会同时知道这两个。在读取/修改/写入周期中将相同的值返回给服务器不应修改该字段,因为服务器应该了解这两种形式。
添加仅输出资源字段
可以添加仅由服务器提供的资源实体中的字段。 服务器可以验证请求中的任何客户端提供的值是否有效,但是如果该值被省略则不能失败。
向后不兼容(断开)更改
删除或重命名服务,字段,方法或枚举值
从根本上说,如果客户端代码可以引用某些东西,那么删除或重命名它是一个突破性的变化,并且必须导致主要的版本增加。 引用旧名称的代码将导致某些语言(如C#和Java)的编译时出现故障,并可能导致其他语言的执行时间故障或数据丢失。 线格式的兼容性在这里是无关紧要的。
更改HTTP绑定
这里的“更改”实际上是“删除和添加”。例如,如果您确定您真的希望支持PATCH,但您发布的版本支持PUT,或者您使用错误的自定义动词名称,则可以添加新的绑定,但是由于所有相同的原因,您不能删除旧的绑定因为删除服务方法是一个突破性的变化。
更改字段的类型
即使新类型与线格式兼容,这可能会改变客户端库的生成代码,因此必须导致主版本的增加。对于编译的静态类型的语言,这可以很容易地引入编译时错误。
更改资源名称格式
资源不能更改其名称 - 这意味着无法更改集合名称。
与大多数突破性更改不同,这也影响主要版本:如果客户端可以期望使用v2.0访问在v1.0中创建的资源,反之亦然,则应在两个版本中使用相同的资源名称。
更精巧的是,有效的资源名称集合也不应该改变,原因如下:
-
如果它变得更加限制,以前成功的请求现在将失败。
-
如果它比以前记录的限制较少,那么根据以前的文档做出假设的客户可能会被打破。客户端很有可能在其他地方存储资源名称,方式可能对允许的字符集和名称的长度敏感。或者,客户端可能会执行自己的资源名称验证来跟踪文档。 (例如,亚马逊给客户带来了许多警告,并且在开始允许更长的EC2资源ID时有迁移期)。
请注意,这样的更改只能在原型文档中可见。因此,当检查CL为破损时,审查非评论变更是不够的。
改变现有请求的可见行为
客户端通常依赖于API行为和语义,即使没有明确支持或记录此类行为。因此,在大多数情况下,更改API数据的行为或语义将被视为消费者的打破。如果行为不是加密隐藏的,那么你应该假设用户已经发现了它,并且会依赖它。例如,用户具有反向设计的AWS EC2资源标识符。
加密分页令牌也是一个好主意(即使数据不感兴趣),以防止用户创建自己的令牌,并在令牌行为发生变化时可能被破坏。
更改HTTP定义中的URL格式
在这里有两种更改可以考虑,超出以上列出的资源名称变化:
-
自定义方法名称:虽然不是资源名称的一部分,但自定义方法名称是REST客户端发布的URL的一部分。更改自定义方法名称不应该破坏gRPC客户端,但是公共API必须假定它们具有REST客户端。
-
资源参数名称:从v1 / shelf / {shelf} / books / {book} tov1 / shelves / {shelf_id} / books / {book_id}改变不会影响替换的资源名称,但可能会影响代码生成。
向资源消息添加读/写字段
客户端经常会执行读/写/写操作。大多数客户不会为他们不知道的领域提供价值,特别是proto3不支持这一点。您可以指定消息类型(而不是基本类型)的任何缺失字段意味着更新不会应用于这些字段,但这使得更难于从实体中显式删除这样的字段值。原始类型(包括字符串和字节)根本不能以这种方式处理,因为在明确指定int32字段为0并且根本不指定之前,proto3没有区别。
如果使用字段掩码执行所有更新,这不是问题,因为客户端不会隐式覆盖其不知道的字段。然而,这将是一个不寻常的API决定:大多数API允许“全部资源”更新。
目录结构
API服务通常使用.proto
文件来定义API表面和.yaml
文件来配置API服务。
每个API服务必须在API存储库中具有包含其定义文件和构建脚本的API目录。
API目录应具有以下标准布局:
-
API目录
-
存储库先决条件
-
BUILD
- 构建文件。 -
METADATA
- 构建元数据文件。 -
OWNERS
- API目录所有者。 -
配置文件
-
{service} .yaml
- 基准服务配置文件,它是google.api.Service
proto消息的YAML表示形式。 -
prod.yaml
- prod delta服务配置文件。 -
staging.yaml
- 分段delta服务配置文件。 -
test.yaml
- 测试增量服务配置文件。 -
local.yaml
- 本地delta服务配置文件。 -
文档文件
-
README.md
- 主要的自述文件。应包含一般生产概况,技术说明等。 -
doc/*
- 技术文档文件。他们应该是Markdown格式。 -
接口定义
-
v[0-9]*/*
- 每个这样的目录都包含主要版本的API,主要是原始文件和构建脚本。 -
{subapi}/v[0-9]*/*
- 每个{subapi}
目录包含子API的接口定义。每个子API可能有自己独立的主要版本。 -
type/*-
包含不同API,不同版本的同一API之间或API与服务实现之间共享的类型的proto文件。类型/*
下的类型定义在发布后不应该有破坏性的更改。
文件结构
应使用proto3 IDL在.proto
文件中定义gRPC API
。
文件结构必须在较低级别和较不重要的项目之前提出更高级别和更重要的定义。 在每个原始文件中,适用的部分应按以下顺序:
-
如果需要,版权和许可证通知。
-
按照该顺序的Proto语法,包,选项和导入语句。
-
API概述文档,为文件的其余部分准备读者。
-
API原始服务定义,按重要性降序排列。
-
资源消息定义。 父资源必须在其子资源之前定义。
-
RPC请求和响应消息定义,按照相应的方法顺序。 每个请求消息必须在其相应的响应消息之前(如果有)。
如果单个原始文件包含整个API表面,则应以API命名:
API | Proto |
---|---|
Library |
library.proto |
Calendar |
calendar.proto |
Large.proto文件可能会分成多个文件。 服务,资源消息和请求/响应消息应根据需要移动到单独的文件中。
我们推荐每个服务一个文件及其相应的请求和响应。 考虑命名此文件<enclosed service name>.proto
。 对于只有资源的原始文件,请考虑将该文件命名为resources.proto
。
原文件名称
Proto文件名应该使用lower_case_underscore_separated_names,并且必须使用扩展名.proto
。 例如:service controller.proto
。
Proto选项
为了在不同的API之间生成一致的客户端库,API开发人员必须在.proto
文件中使用一致的原始选项。 符合本指南的API定义必须使用以下文件级原始选项:
syntax = "proto3";
//包名应以公司名称开头,以
//主要版本。
package google.abc.xyz.v1;
//此选项指定要在C#代码中使用的命名空间。 这是默认值
//到PascalCased版本的proto包,这是很好的
//包名称由单字段组成。
//例如,一个名为“google.shopping.pets.v1”的包将使用C#
//“Google.Shopping.Pets.V1”的命名空间。
//但是,如果程序包名称的任何段由多个单词组成,
//这个选项需要被指定,以避免只有第一个字
//大写。 例如,Google Pet Store API可能具有包名称
//“google.shopping.petstore.v1”,这意味着C#的命名空间
//“Google.Shopping.Petstore.V1”。 相反,应该使用该选项
//正确地将其大小写为“Google.Shopping.PetStore.V1”。
//有关C#/。NET大小写规则的更多详细信息,请参阅[框架设计]
//指南](https://msdn.microsoft.com/en-us/library/ms229043)。
option csharp_namespace = "Google.Abc.Xyz.V1";
//此选项让proto编译器在程序包中生成Java代码
//名称(见下文),而不是外部类。 它创建一个更简单
//通过减少一级名称嵌套的开发人员体验
//与大多数不支持外部类的编程语言一致。
option java_multiple_files = true;
// Java外部类名应该是UpperCamelCase中的文件名。 这个
//类只用于保存原始描述符,所以开发人员不需要
//直接使用它
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";
//从包中生成的Objective-C符号的合理前缀。
//应该至少有3个字符长,全部大写和约定
//是使用包名称的缩写。 有点短,但是
//希望足够独特,不会与可能出现的事情冲突
// 未来。 'GPB'保留给协议缓冲区实现本身。
option objc_class_prefix = "GABCX";
词汇表
联网API
- 应用程序编程通过计算机网络运行的接口。它们使用包括HTTP在内的网络协议进行通信,并且经常由不同的组织生成,而不是消费它们。
Google API
- 由Google服务公开的网络API。他们大多数托管在googleapis.com域上。它不包括其他类型的API,如客户端库和SDK。
API接口
- 协议缓冲区服务定义。它通常映射到大多数编程语言中的接口。 API接口可以由任何数量的API服务来实现。
API版本
- API接口的版本或一组API接口,如果它们一起定义。 API版本通常由字符串(例如“v1”)表示,并呈现在API请求和协议缓冲区包名称中。
API方法
- API接口内的单独操作。它通过rpc定义在协议缓冲区中表示,并且通常在大多数编程语言中映射到API接口中的函数。
API请求
- 单一调用API方法。它经常用作计费,记录,监控和速率限制的单位。
API服务
- 在一个或多个网络端点上暴露的一个或多个API接口的部署实现。 API服务由其与RFC 1035 DNS兼容的服务名称(如calendar.googleapis.com)标识。
API端点
- 指的是API服务用于服务实际API请求的网络地址,例如pubsub.googleapis.com和content-pubsub.googleapis.com。
API产品
- API服务加相关组件,如服务条款,文档,客户端库和服务支持,作为产品集体呈现给客户。例如Google Calendar API。注意:人们有时将API产品简称为API。
API服务定义
- 用于定义API服务的API接口定义(.proto文件)和API服务配置(.yaml文件)的组合。
API消费者
- 消费API服务的实体。对于Google API,通常是拥有客户端应用程序或服务器资源的Google项目。
API制作人
- 实体生成API服务。对于Google API,它通常是拥有API服务的Google项目。
API后端
- 一组服务器以及实现API服务业务逻辑的相关基础架构。单个API后端服务器通常称为API服务器。
API前端
- 一组服务器和相关基础架构,可跨API服务提供通用功能,例如负载平衡和身份验证。单个API前端服务器通常称为API代理。注意:API前端和API后端可能彼此相邻或彼此远离运行。在某些情况下,它们可以编译成单个应用程序二进制文件,并在单个进程中运行。