Google API Design Guide

标签(空格分隔): google restful api design


当前版本的API设计指南发布时间:2017-02-21

介绍

这是一份关于网络API如何设计的通用指南,自2014年起Google内部在设计Cloud API和其他Google API时一直遵循此指南。在此共享此设计指南,为外部开发人员提供指导,以此使我们更容易协同工作。

Google Cloud Endpoints开发人员在设计gRPC API时可能会发现本指南特别有用,我们强烈建议此类开发人员使用这些设计原则,但是,我们也不强制要求,您可以在不遵循指南的情况下使用Cloud Endpoints和gRPC。

本指南适用于REST API和RPC API,尤其聚焦于gRPC API设计。gRPC API使用Protocol Buffers定义其API surface和使用API Service Configuration配置其API服务,包括HTTP映射,logging和monitoring. Google API和Cloud Endpoints gRPC API使用HTTP映射功能进行JSON/HTTP到Protocol Buffered/RPC转码。

随着新风格和设计模式的采用和批准,本指南随着时间推移将进行更新与补充,秉持这种精神,它永远不是最终版,并且API设计的艺术和工艺永远都会有提升的空间。

本文档中使用的约定

用于此文档的“要求级别”关键字,包括“必须”,“绝不”,“必需”,“应该”,“不应该”,“应该”,“不应该”,“推荐”,“可以”和“可选”,将按RFC 2119中的描述进行解释。

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

面向资源设计

本设计指南的目标是帮助开发人员设计简单,一致且易于使用的网络API,同时覆盖socket-based RPC APIs和 HTTP-based REST APIs。

以前,人们根据API接口和方法设计RPC API,例如CORBA和Windows COM,随着时间的推移,系统引入了越来越多的接口和方法,最终结果可能对于绝大多数的接口和方法,每个接口和方法都与其他的接口和方法不同。开发人员必须仔细学习每一个,以便正确使用它,这既费时又容易出错。

REST的架构风格于2000年首次被提出,主要用于与HTTP/1.1配合使用。其核心原则是定义可以被少量method操作的名称资源,资源和method称为API的名词和动词。使用HTTP协议,资源名称自然地映射到URL,method自然地映射到HTTP方法POSTGETPUTPATCHDELETE

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

然而虽然HTTP REST API在Internet上非常流行,但它们承载的流量比传统的RPC API要小。例如,美国在高峰时段大约有一半的互联网流量是视频内容,很少有人会考虑使用REST API来提供此类内容,这是出于性能原因。在Data Center内部,许多公司使用socket-based RPC APIs来承载大部分网络流量,这可能比公共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-based APIs,它们只是映射到最合适的HTTP谓词。

设计流程

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

  • 确定API提供什么类型资源。
  • 确定资源之间的关系。
  • 根据类型和关系确定资源名称方案。
  • 确定资源模式。
  • 将最少的方法集附加到资源。

Resource

面向资源API通常被建模为层次资源结构,其中每个节点是一个简单资源或一个资源集,为简单起见,它们通常相应地被称为资源和集合。

集合包含了一组相同类型的资源,例如,用户拥有一组联系人。资源有多个​​状态及0或多个子资源,每个子资源可以是简单资源或资源集,例如,Gmail API有一组用户,每个用户都有一组消息,一组线程,一组标签,一个配置文件和几个设置项。

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

Methods

面向资源的API的关键特性是它强调资源(数据模型)而不是资源上执行的方法(功能)。典型的面向资源的API使用少量方法暴露大量资源。 方法可以是标准方法或自定义方法。 对于本指南,标准方法是:List, Get, Create, Update, Delete.

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

示例

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

Gmail API

Gmail API服务实现了Gmail API并暴露了大部分Gmail功能,它具有以下资源模型:

  • API服务:gmail.googleapis.com
  • 用户集: users/*.每个用户都有以下资源:
    • 消息集: users/*/messages/*.
    • 线程集: users/*/threads/*.
    • 标签集: users/*/labels/*.
    • 变更历史集: users/*/history/*.
    • 用户profile: users/*/profile.
    • 用户settings: users/*/settings.
Cloud Pub/Sub API

pubsub.googleapis.com服务实现了[Cloud Pub/Sub API](https://cloud.google.com/pubsub),它定义了以下资源模型:

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

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

Cloud Spanner API

spanner.googleapis.com服务实现了Cloud Spanner API, 该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应使用scheme-less URIs作为资源名称,它们通常遵循REST URL规范,与网络文件路径非常相似,它们可以轻松映射到REST URLs:有关详细信息,请参阅Standard Methods部分。

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

资源名称使用集合ID和资源ID按层次结构组织,以正斜杠分隔,如果资源包含子资源,则通过父资源名称加子资源的ID来形成子资源的名称 - 中间由正斜杠分隔。

示例1:storage服务有一组bucket,其中每个bucket 都有一组objects

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

示例 2: email服务有一组用户,每个用户有一个settings子资源,settings子资源有许多其他的子资源,包括customFrom:

API服务名称 集合ID 资源ID 集合ID 资源ID
//mail.googleapis.com /users /[email protected] /settings /customFrom

API设计人员可以使用任何合理的值作为资源或集合的ID,只要这个值在资源层次结构中是唯一的,下面有更多指南关于如何选择适当的资源和集合ID。

假设资源名称除分隔符外没有包含任何其他/,通过拆分资源名称,例如name.split("/")[n],可以获取到单个集合ID和资源ID。

全路径资源名称

scheme-less URI包含DNS兼容的(DNS-compatible) API服务名称和资源路径,资源路径也称为相对资源名称。例如:

"//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段。 例如:

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

API服务应尽可能使用URL友好的资源ID。无论资源ID是由客户端还是服务器指定,都必须明确地文档化。例如,文件名通常由客户端分配,而电子邮件消息ID通常由服务器分配。

Collection ID

Collection ID为一个非空URI段(segment-nz-nc) ,用于标识集合中的一个资源,请参见上面示例。

由于collection IDs经常出现在生成的客户端libraries中,因此它们必须符合以下要求:

  • 必须是有效的C/C ++标识符。
  • 必须是驼峰形式的复数,如果该术语没有合适的复数形式,如“evidence”和“weather”,则使用单数形式。
  • 必须使用简明扼要的英语术语。
  • 应避免过于笼统的术语,例如:rowValues比value更好,应无条件避免以下术语:
    • elements
    • entries
    • instances
    • items
    • objects
    • resources
    • types
    • values

Resource Name vs URL

虽然完整的Resource Name类似于一般的URL,但它们并不一样,单个资源可以通过不同的API版本、API协议或API网络端点暴露出去,完整Resource Name未包含此类信息,因此必须将其映射到特定的API版本和API协议才能实际使用。

想通过REST API使用完整Resource Name,Resource Name必须通过在服务名称前添加HTTPS协议、在资源路径之前添加API major version、URL转义资源路径来转换为REST URL。 例如:

// 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"

Resource Name as String

除非向后兼容性存在问题,否则Google API必须使用字符串表示Resource Name, 且应该像处理普通文件路径一样处理Resource Name,Resource Name不支持%-encoding。

对于资源定义,第一个字段应该是称为name的一个字符串,用于表示资源名称。
注意:应限定其他与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;
}

注意:为了保证资源名称的一致性,任何URL模板变量都不能捕获前导/,例如,必须使用URL模板/v1/{name=shelves/*/books/*}而非/v1{name=/shelves/*/books/*}.

问题

Q: 为什么不使用资源ID来识别资源?

A:任何大型系统,都包含多种资源,若使用资源ID来标识资源,我们需要根据每个资源指定元组来标识,例如(bucket,object)(user,album,photo),它会产生如下几个问题:

  • 开发人员必须了解并记住这些匿名元组。
  • 传递元组通常比传递字符串更难。
  • 集中式基础架构(如日志记录和访问控制系统),不能识别专门的元组。
  • 专门的元组限制了API设计的灵活性,例如提供可重用的API接口。 例如, Long Running Operations 可以与许多其他API接口一起使用,因为它们使用了灵活的资源名称。
Q: 为什么特殊域叫做name而不是id

因为资源“名称”概念,所以特殊域叫name,一般来说,我们发现name概念让开发人员感到困惑,例如,文件名实际上只是文件名称还是文件的完整路径?通过保留标准字段name,开发人员不得不选择更合适的术语,例如display_nametitlefull_name

标准方法

本章定义了标准方法的概念,即List, Get, Create, Update, 和 Delete. 标准方法可降低复杂性并提高一致性。 Google APIs repository 中超过70%的API方法都是标准方法,这使得它们更易于学习和使用。

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

Standard Method HTTP Mapping HTTP Request Body HTTP Response Body
List GET N/A Resource* list
Get GET N/A Resource*
Create POST Resource Resource*
Update PUT or PATCH Resource Resource*
Delete DELETE N/A google.protobuf.Empty**

如果方法支持响应字段掩码且指定了要返回的字段子集,List, Get, Create, 和Update方法返回的资源可能包含部分数据。在某些情况下,API平台本身支持所有方法的字段掩码。

不立即删除资源的Delete方法(如只是更新状态标志或创建一个long-running的删除操作)返回的响应包含这个long-running操作或修改后的资源。

对于在单个API调用时间跨度内未完成的请求,标准方法还可以返回一个 long running operation 。

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

List

List方法传递collection名称和0或多个其他参数作为输入,并返回与输入匹配的资源列表。

List通常用于资源搜索,适用于搜索单个集合的数据,该集合大小有限且未缓存。对于更广泛的情况,使用custom method Search

批量获取(例如,一个方法接收多个资源ID值且为每个ID返回对应的对象)应该通过自定义BatchGet方法来实现,而非List。但是,如果您已提供相同功能的List方法,则可以重用List方法。如果您使用的是自定义BatchGet方法,则应将其映射到HTTP GET

适用的常见模式: pagination, result ordering.

适用的命名约定:filter field, results field

HTTP映射:

  • List方法必须使用HTTP GET动词。
  • 请求消息字段中的集合名称(资源列表名称),必须映射到URL路径上,如果集合名称映射到URL路径,则URL模板的最后一段(the collection ID)必须是字面常量。
  • 所有剩下的请求消息字段映射到URL查询参数。
  • 没有请求body,API配置不得声明body条款。
  • 响应body包含资源列表以及可选元数据。

例:

// 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

Get方法传递资源名称,0或多个其他参数,并返回对应的资源对象。

HTTP映射:

  • Get方法必须使用HTTP GET动词。
  • 请求消息字段中的资源名称应映射到URL路径。
  • 所有剩下的请求消息字段应映射到URL查询参数。
  • 没有请求body, API配置不得声明body条款。
  • 响应body即为返回的资源。

例:

// 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

Create方法传递父资源名称,资源以及0或多个其他参数,在指定的父资源下创建新资源,并返回新创建的资源。

如果API支持创建资源,则应该为每一种可创建的资源新增一个Create方法。

HTTP映射:

  • Create方法必须使用HTTP POST动词。
  • 请求消息包含父资源名称,在该父资源下创建资源。
  • 所有剩下的请求消息字段应映射到URL查询参数。
  • 该请求可能包含名为_id的字段,以允许调用者传客户端选择分配的ID,该字段必须映射到URL查询参数。
  • 请求消息字段'资源'应映射到请求body,如果Create方法使用了body HTTP配置条款,必须使用body: 形式。
  • 响应body即为返回的资源。

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

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

The Create method must take an input resource, so that when the resource schema changes, there is no need to update both request schema and resource schema. For resource fields that cannot be set by the clients, they must be documented as "Output only" fields.

// 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 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方法接收包含了资源、0或多个参数的request消息,它更新资源及其属性,并返回更新后的资源对象。

除了资源名称或其父级之外,可变资源属性应该都能被Update方法更新,任何对资源进行renamemove的操作都不能出现在Update方法中,而由自定义方法处理。

HTTP映射:

  • 标准Update方法支持只对资源部分属性进行更新,使用HTTP PATCH与名为update_maskFieldMask属性一起实现(patch只更新传入的属性,原有属性不变,put一般更新除ID外的整个对象,对象不存在,patch能创建新对象,put不能,put幂等,Post每次创建)。
  • 需要更加高级patching语义的Update方法(例如附加到重复字段),应该由自定义方法.实现。
  • 如果Update方法仅支持全部的资源更新,必须使用HTTP PUT,但是,强烈建议不要进行全更新,因为新增资源字段时会出现向后兼容问题。
  • 资源名称字段必须映射到URL路径,该字段也可能同时在资源消息体中。
  • 资源对象必须置于请求body。
  • 所有其他的请求消息字段必须映射到URL查询参数(?xxx=xxx&)。
  • 响应消息必须是更新后的资源。

如果API接受客户端指定的资源名称,则服务端可以允许客户端指定不存在的资源名称并创建新资源,否则,对于不存在的资源名称,Update方法必须失败,且返回错误代码NOT_FOUND

若API具有支持资源创建的Update方法,还提供Create方法,如果Update方法是唯一的创建资源方法,调用方会不清楚如何创建资源。

例:

// 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方法接收资源名称、0或多个参数,删除或计划删除(即只是下发删除指令,具体什么时候删除由server端具体实现)指定的资源,Delete方法返回google.protobuf.Empty

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

HTTP映射:

  • Delete方法必须使用HTTP DELETE谓词。
  • 资源名称字段映射到URL路径。
  • 所有其他请求字段映射到URL query参数。
  • 没有请求body, API配置不得声明body条款。
  • 如果Delete方法立即删除资源,则返回空响应。
  • 如果Delete方法启动long-running操作,它应该返回long-running操作。
  • 如果Delete方法仅将资源标记为已删除,则返回更新后的资源。

Delete方法的调用应该是幂等的,但不必产生相同的响应,任意次的Delete请求都应该导致(最终)能删除资源,但只有第一次请求才会返回成功码,后续请求应返回google.rpc.Code.NOT_FOUND

// 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设计时如何使用自定义方法。

自定义方法只应用于通过标准方法无法轻易表达的功能, 通常,API设计者尽可能选择标准方法而不是自定义方法。标准方法具有大多数开发人员熟悉的更简单且定义良好的语义,因此它们更易于使用且不易出错。 标准方法的另一个优点是API平台能更好地理解和支持标准方法,例如计费,错误处理,日志记录,监控。

自定义方法可以与一个资源,一个集合或一个服务相关联,它可能需要任意请求并返回任意响应,并且还支持流请求和响应。

自定义方法名称必须遵循 method naming conventions.

HTTP映射

对于自定义方法,必须使用以下通用HTTP映射:
https://service.name/v1/some/resource/name:customVerb
使用:代替/来分割自定义谓词与资源名称的原因是,为了支持任意类型路径,undelete一个文件可以映射为POST /files/a/long/file/name:undelete

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

  • 自定义方法应该使用HTTP POST,因为它具有最灵活的语义,但用于替代get或list的方法除外,它们可能使用GET。(详见第三章)
  • 自定义方法不应使用HTTP PATCH,但可以使用其他HTTP谓词,在这种情况下,方法必须遵循该动词的标准HTTP语义。
  • 值得注意的是,使用HTTP GET的自定义方法必须是幂等的,没有副作用。例如,在资源上实现特殊视图的自定义方法,使用HTTP GET
  • 资源或集合的资源名称字段映射到URL路径。
  • URL路径必须以冒号后跟custom verb的后缀结尾。
  • 如果用于自定义方法的HTTP谓词允许HTTP请求body(POSTPUTPATCH或自定义HTTP谓词),则此类自定义方法的HTTP配置必须使用body:“*”条款,所有其他的请求消息字段映射到HTTP请求body。
  • 如果用于自定义方法的HTTP谓词不接受HTTP请求body(GETDELETE),则此方法的HTTP配置不能使用body条款,所有剩余的请求消息字段都映射到URL查询参数。

WARNING: 如果一个service实现了多个API,API生产者必须非常小心创建服务配置,以避免API之间的自定义动词冲突。

// 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"
  };
}

Use Cases

一些特殊场景,自定义方法可能是正确选择:

  • 重启虚拟机.设计备选方案可能是“在reboots集合中创建reboot 资源”,感觉极其复杂,或者“虚拟机具有可变状态,客户端可以将其从RUNNING更新到RESTARTING”,这将引入还可能有哪些状态间转换的问题。此外,重启是一个众所周知的概念,可以很好地转换为直观的且满足开发人员期望的自定义​​方法。
  • 发送邮件.创建电子邮件不一定要发送(草稿),与设计替代方案(将消息移动到“Outbox”集合)相比,自定义方法具有更易被API用户理解且能更直观对概念进行建模的优点。
  • 晋升员工.如果以标准update实现,client端必须复制企业管理晋升流程的政策,以确保晋升在同一职业阶梯内达到正确的级别。
  • 批处理方法.对于性能关键方法,提供自定义批处理方法以减少每个请求的开销可能很有用。例如,accounts.locations.batchGet.

一些标准方法比自定义方法更合适的示例:

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

通用自定义方法

下面列出了常用或有用的自定义方法名称的精选列表。 API设计者在引入自己的名称之前应考虑这些名称,以促进API之间的一致性。

Method Name Custom verb HTTP verb Note
Cancel :cancel POST 取消未完成的操作 (构建,计算等.)
BatchGet<复数名词> :batchGet GET 批量获取多个资源. (见详情 the description of List)
Move :move POST 将资源从一个父节点移动到另一个
Search :search GET List的替代方法,用于获取不符合List语义的数据。
Undelete :undelete POST .还原以前删除的资源, 建议的保留期为30天。

标准字段

本节描述了一组标准的消息字段定义,供具有类似概念时使用,以确保相同的概念在不同的API中具有相同的名称和语义。

Name Type Description
name string name字段应包含相对资源名称.
parent string 对于资源定义和List/Create请求,parent字段应包含父级相对资源名称.
create_time Timestamp 实体创建时间
update_time Timestamp 实体最近更新时间. 注意: 当执行create/patch/delete操作时需要更新update_time.
delete_time Timestamp 实体的删除时间戳,仅当逻辑删除时.
expire_time Timestamp 实体到期时的时间戳
start_time Timestamp 标记某个时间段开始的时间戳
end_time Timestamp 标记某个时间段或操作结束的时间戳(无论其成功与否)
read_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.
mime_type string IANA发布的MIME类型(也称为media type). 更多信息参考https://www.iana.org/assignments/media-types/media-types.xhtml.
display_name string 实体的显示名称
title string 实体的正式名称,例如公司名称,被视为display_name的正式版本
description string 实体的一个或多个文本描述段落
filter string List方法的标准过滤器参数
query string 如果应用于搜索方法(ie :search),则与“filter”相同
page_token string List请求中的分页标记
page_size int32 List请求中的分页大小
total_size int32 列表中的记录总数,与分页无关
next_page_token string List response中的下一分页标记,作为下一页请求的page_token的值, 空值表示当前为最后一页。
order_by string 指定List请求的结果如何排序
request_id string 用于检测重复请求的字符串,具有唯一性
resume_token string 用于恢复流式传输请求的不透明令牌
labels map 表示云资源标签
deleted bool 如果资源允许取消删除行为,则必须具有 deleted字段,标示资源已删除。
show_deleted bool 如果资源允许取消删除行为,则相应的List方法必须具有show_deleted字段,以便客户端可以发现已删除的资源。
update_mask FieldMask 它用于Update请求消息,用于对资源部分更新。此掩码与资源相关,而不是与请求消息相关
validate_only bool 如果为true,则表示仅应验证请求,而不执行

Errors

本章概述了Google APIs错误模型以及提供了开发人员如何正确生成和处理错误的通用指导。

Google APIs使用简单的协议无关的错误模型,它允许我们跨不同的APIs、API协议(如gRPC或HTTP)和错误上下文(例如,异步,批处理或工作流错误)暴露一致的体验。

Error Model

错误模型由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 APIs使用resource-oriented的设计方式,因此错误处理遵循同样的设计原则,对大量资源使用一小组标准错误。例如,server端使用一个标准的google.rpc.Code.NOT_FOUND 错误码、而非定义不同类型的“not found”错误来告诉客户端找不到哪个特定资源。较小的状态空间降低了文档的复杂性,在客户端库中提供了更好的惯用映射,并降低了客户端逻辑复杂性,同时不限制包含actionable信息。

Error Codes

Google API 必须使用由google.rpc.Code定义的规范错误码。单个API 应该避免定义其他错误代码,因为开发人员不太可能编写逻辑来处理大量错误码。作为参照,若每个API调用平均处理3个错误码,那意味着大部分应用逻辑只是用于错误处理,这不是一个好的开发体验。

Error Messages

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

  • 不要假设每个用户都是您API的专家用户,用户可能是client开发人员,运维人员,IT staff甚至应用终端用户。
  • 不要假设用户对您的服务实现细节了如指掌,或者熟悉错误的上下文(例如日志分析)。
  • 如果可能,构建的错误消息应能帮技术用户(但不一定是API的开发人员)响应错误并修正错误。
  • 保持错误消息简洁。如果有必要,提供一个链接,让对错误困惑的人可以提出问题、提交反馈或获取错误消息的更详细的信息,否则,使用详细信息字段进行扩展。

Error Details

Google APIs为错误详情定义了一组标准错误payloads(有效负载),您可以在google/rpc/error_details.proto.中找到相关信息。这些内容涵盖了API错误最常见的需求,例如配额失败和无效参数。与错误代码一样,错误详情尽可能使用这些标准有效负载。

除非可以帮助应用代码处理error,否则不要引入其他错误详情类型。如果错误只能靠人工依赖错误消息内容进行处理,那就让开发人员手动处理,而不是引入新的错误详情类型。

以下是一些error_details 有效负载示例:

  • RetryInfo 描述客户端何时可以重试失败(可能返回了Code.UNAVAILABLECode.ABORTED)的请求
  • QuotaFailure 描述了配额检查失败(可能返回了Code.RESOURCE_EXHAUSTED)原因
  • BadRequest描述了客户端违规请求,可能返回了Code.INVALID_ARGUMENT

HTTP Mapping

虽然proto3消息模块具有原生JSON编码,Google API平台为Google JSON REST APIs使用了不同的错误Schema,以实现向后兼容。

Schema:

// 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;
}

示例:

{
  "error": {
    "code": 401,
    "message": "Request had invalid credentials.",
    "status": "UNAUTHENTICATED",
    "details": [{
      "@type": "type.googleapis.com/google.rpc.RetryInfo",
      ...
    }]
  }
}

你可能感兴趣的:(Google API Design Guide)