标准方法
本章阐述标准方法的概念,包括了List
, Get
, Create
, Update
, and Delete
。很多不同类型的API都拥有非常类似的语义,把它们归纳为标准方法能够显著降低复杂度并提高一致性。在谷歌API仓库中,超过70%的API属于标准方法。标准方法更容易学习和使用。
以下表格描述了如何将标准方法映射为REST方法,也就是所谓的CRUD方法:
方法 | HTTP 方法映射 | HTTP 请求体 | HTTP 返回体 |
---|---|---|---|
List | GET <集合URL> | 空 | 资源* 列表 |
Get | GET <资源URL> | 空 | 资源* |
Create | POST <集合URL> | 资源 | 资源* |
Update | PUT or PATCH <资源URL> | 资源 | 资源* |
Delete | DELETE <资源URL> | 空 | 空** |
*如果方法支持字段掩码并指定要返回字段的子集时,,从List
,Get
,Create
和Update
方法返回的资源可能包含部分数据。在一些情况下,API平台的所有方法都支持字段掩码。
**Delete方法如果并没有立刻删除响应的资源(例如创建一个耗时删除操作或者更新标识),它的响应应该包括耗时操作(译者注:耗时操作可看做是对服务器端长时间运行过程的抽象。因为运行过程耗时长。为了不阻塞客户端同时给客户端跟踪运行状况的机会,可以先给调用方返回一个对象,这个对象对应服务器端的执行过程,可以用它获取远程操作的状态和结果)或更新后的资源。
如果请求无法在单个API调用时间段内完成时,标准方法可以返回一个耗时操作。
以下章节描述了各标准方法的细节。范例中使用 .proto 文件定义方法,HTTP映射则通过特殊注释表明。你会发现Google APIs仓库中有很多使用标准方法的案例。
List
List
方法接受一个集合名,零或者多个参数,根据输入返回相应的资源列表。它也经常被用作搜索资源。
List
适用于量有限且无缓存的单一集合数据查询;若需要更广的应用,应该用自定义方法Search
。
批量获取(如接受多个资源ID并返回每个ID对象的方法)应该使用自定义方法BatchGet
实现,而不是List
方法。但如果你已经有了提供相似功能的List
方法,你也可以继续使用。如果你使用自定义的BatchGet
方法,应该确保它映射为HTTP GET方法。
HTTP 映射
- List方法必须使用HTTP
Get
方法。 - 请求消息字段接收资源名称集合,而相关资源应该映射为URL路径。如果集合名称映射到URL路径,URL模板中的最后一段(即集合ID)必须为文字。
- 其他所有请求消息字段应当映射为URL请求参数(译者注:大意是资源名字段映射到URL路径,其他映射到请求参数,参考统一资源定位符中。
- 没有请求体,即API配置中不应该声明请求体。
- 返回体应该包含资源集合以及可选的元数据。
// 列举给定书架上的所有书
rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
// List方法映射为HTTP GET。
option (google.api.http) = {
// 在`parent`指定父级资源名,如"shelves/shelf1"。
get: "/v1/{parent=shelves/*}/books"
};
}
message ListBooksRequest {
// 父级资源名,如"shelves/shelf1"
string parent = 1;
// 返回的最大数据条数
int32 page_size = 2;
// 上一次List请求返回的next_page_token值,如果有的话
string page_token = 3;
}
message ListBooksResponse {
// 字段名必须跟方法名中的"books"一致
// 数据返回的最大条数由请求中的page_size属性值制定
repeated Book books = 1;
// 获取下一页数据的令牌,如果没有更多数据则为空.
string next_page_token = 2;
}
Get
Get
方法接受一个资源名,零到多个参数,返回指定的资源。
HTTP 映射
Get
方法必须使用HTTP Get方法。- 接收资源名称的请求消息字段(可多个)应该映射到URL路径中。
- 其他所有请求消息字段应当映射到URL查询参数中。
- 无请求体,即API配置中绝对不可以出现请求体声明。
- 返回资源应当映射到返回体中。
// 获得指定书籍
rpc GetBook(GetBookRequest) returns (Book) {
// Get映射为HTTP GET,资源名绑定到URL中,无请求体
option (google.api.http) = {
// 注意URL模板中有多个片段包括多个变量,以指定书籍相应的不同资源名,例如:
// "shelves/shelf1/books/book2"
get: "/v1/{name=shelves/*/books/*}"
};
}
message GetBookRequest {
// 请求的资源名,如:
// "shelves/shelf1/books/book2"
string name = 1;
}
Create
Create
方法接受一个集合名,一个资源,并且有零或多个参数;然后在相应集合中创建新的资源,最后返回新创建的资源。
如果API支持创建资源,那么它应该有Create
方法用于创建各种类型的资源。
HTTP 映射
Create
方法必须使用HTTP POST方法。- 请求消息应该有一个名为
parent
的字段,以接受父级资源名,当前资源将在父资源下创建。 - 其他所有请求消息字段应当映射到URL查询参数中。
- 请求可以包括一个名为\
_id的字段,以允许调用方选择客户端分配的ID(译者注:Create方法创建的资源id可以是客户端生成的);此字段必须映射为URL查询参数。 - 包含资源的请求消息字段应该映射到请求体中,如果HTTP子句用于Create方法,则必须使用:\
的表单。 - 返回的资源应当映射到整个返回体中。
如果Create
方法支持客户端指定资源名,并且相应资源已经存在;那么它应该返回错误(推荐使用google.rpc.Code.ALREADY_EXISTS错误代码),或使用其它服务器指定的资源名:文档中需要清晰说明被创建的资源名可能跟传入的资源名不同。
rpc CreateBook(CreateBookRequest) returns (Book) {
// Create映射为HTTP POST,集合名映射到URL路径
// HTTP请求体包含资源
option (google.api.http) = {
// 在`parent`指定父级资源名,如"shelves/shelf1"。
post: "/v1/{parent=shelves/*}/books"
body: "book"
};
}
message CreateBookRequest {
// 待创建book资源所属的父级资源名。
string parent = 1;
// 此书的ID
string book_id = 3;
// 待创建的book资源
// 字段名必须跟方法名中名词一致
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
方法应该支持部分资源更新,并使用 HTTPPATCH
方法以及名为update_mask
的FieldMask
字段。 - 如果
Update
方法需要更高级的修复语义,比方说给重复字段增补新值,那么应该使用自定义方法。 - 如果
Update
方法仅支持完整的资源更新,它必须使用HTTPPUT
;但是强烈不推荐这么做,因为这会造成添加新资源字段时的兼容性问题。 - 接受资源名的字段必须映射到URL路径中;字段也可以包含在资源消息中。
- 包含资源的请求消息中字段必须映射到请求体中。
- 其他所有请求消息字段必须映射到URL查询参数中。
- 返回的结果必须是更新后的资源。
如果API允许客户端指定资源名,服务器可以允许客户端指定一个不存在的资源名并创建新的资源。否则,使用不存在的资源名时Update
方法应该报错。如果不存在资源是唯一的错误条件,那么错误码应该用NOT_FOUND
。
API如果有Update
方法,并且支持资源创建的话,就应该提供Create
方法;以避免调用者误以为Update
方法是创建资源的唯一方式。
rpc UpdateBook(UpdateBookRequest) returns (Book) {
// Update 映射为HTTP PATCH。资源名映射到URL路径。
// HTTP请求提包含资源
option (google.api.http) = {
// 注意URL模板中的变量指定了待更新的book资源名
patch: "/v1/{book.name=shelves/*/books/*}"
body: "book"
};
}
message UpdateBookRequest {
// 用于更新服务器上资源的book数据
Book book = 1;
// 用于更新资源的掩码
FieldMask update_mask = 2;
}
Delete
Delete
方法接受一个资源名,零或多个参数;然后删除,或者安排删除相应的资源。Delete
方法应该返回google.protobuf.Empty
。
注意API不应该依赖于Delete
方法返回的任何信息,因为它不能被反复调用(译者注:因为资源可能已经被删除了)。
HTTP 映射
Delete
方法必须使用HTTPDELETE
方法。- 对应于资源名称的请求消息字段(可多个)应该绑定到URL路径中。
- 其他所有请求消息字段应当映射到URL查询参数中。
- 无请求体,即API配置中绝对不可以出现请求体声明。
- 如果
Delete
方法立刻删除除资源,它应该返回空。 - 如果
Delete
方法开启了一个耗时操作,它应该返回这个耗时操作(译者注:本节开始的译者注中提到了耗时操作)。 - 如果
Delete
方法仅是把资源标记为删除,它需要返回更新后的资源
调用Delete
方法必须是幂等的,但返回值可以不同。任意次数的Delete
请求应当使得一个资源(最终)被删除,但只有第一次请求获得成功的返回值,后续的请求应当返回google.rpc.Code.NOT_FOUND
.
rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {
// Delete 映射为HTTP DELETE方法,资源名绑定到URL路径中。
// 没有请求体。
option (google.api.http) = {
// 注意URL模板中有多个片段包括多个变量,以指定待删除书籍相应的不同资源名,例如:
// "shelves/shelf1/books/book2"
delete: "/v1/{name=shelves/*/books/*}"
};
}
message DeleteBookRequest {
// 等待删除的book数据资源名,如:
// "shelves/shelf1/books/book2"
string name = 1;
}