本篇摘选自《Google API Design Guide》。
面向资源设计
API设计要遵循简单、一致、易用的原则。
在REST架构中资源与方法可视为API的动词与名词;在HTTP REST API中,资源名称对应于URL,而方法对应于HTTP中的GET/POST/PUT/PATCH/DELETE
。
REST API
一组REST API被建模为一组可独立寻址的资源,并通过资源名称被引用,通过一组方法来操作。
设计流程
面向资源的API设计可使用以下步骤:
- 确定API提供什么类型的资源;
- 确定资源间的关系;
- 根据资源类型和关系确定资源命名方案;
- 明确资源的schema;
- 给资源添加最少的方法集;
资源
面向资源的API通常被建模为一个资源层次结构;每个节点可以是一个简单资源或一组资源。资源组为:
- 资源组包含相同类型的一系列资源;
- 资源有相同的状态,可以有零个或多个子资源。
方法
典型的面向资源的API通过少量方法暴露大量资源;常用的有List/Get/Create/Update/Delete
。
资源命名
资源名称是资源的标识符,每个资源必须有其唯一的资源名称。资源名称由资源ID本身、父资源的ID和资源对应的API服务名称组成。
资源组是一种特殊的资源,它包含一组相同类型的子资源。资源名称使用组ID和资源ID分层组织,以斜杠/
分割。
如一个存储服务有一组bucket
,每一个bucket有一组objects
:
API资源名称 | 组ID | 资源ID | 组ID | 资源ID |
---|---|---|---|---|
//storage.googleapis.com | /buckets | bucket-id | objects | object-id |
资源ID
资源ID也会出现在客户端库里,需满足以下要求:
- 必须是有效的C/C++标识符;
- 必须是驼峰命名的(组资源名称要使用复数形式),首字母小写;
- 必须使用清晰建模的英文词语;
- 避免或者限定过于笼统的词语:如未限定的Element、Item、Value、Type等。
标准方法
标准方法映射为REST方法:
方法 | HTTP 方法映射 | HTTP 请求体 | HTTP 返回体 |
---|---|---|---|
List | GET <集合URL> | 空 | 资源* 列表 |
Get | GET <资源URL> | 空 | 资源* |
Create | POST <集合URL> | 资源 | 资源* |
Update | PUT or PATCH <资源URL> | 资源 | 资源* |
Delete | DELETE <资源URL> | 空 | 空** |
List
List方法接受一个集合名,零或多个参数,根据输入返回相应的资源列表;适用于数量有限且无缓存的单一集合数据查询。更广泛的应用,应自定义方法Search
。
批量获取(接受多个资源ID,并返回对应的每个对象)应使用自定义方法BatchGet
,且映射为HTTP Get
方法。
自定义
自定义方法命名(非标准方法不能满足要求时,要使用一些常见或有用的自定义方法):
方法名 | 自定义动词 | HTTP动词 | 备注 |
---|---|---|---|
Cancel | :cancel | POST | 取消一个未完成的操作(构建,计算等等) |
BatchGet | :batchGet | GET | 批量获取多个资源 |
Move | :move | POST | 将一个资源从一个父级移到另一个 |
Search | :search | GET | List 的语义不足够时,搜索获取数据 |
Undelete | :undelete | POST | 恢复之前删除的数据;推荐的数据的保留时间是30天。 |
标准字段
标准消息字段定义:
字段名 | 类型 | 描述 |
---|---|---|
name | string | name字段应该包含相对资源名 |
parent | string | 对于资源定义和List /Create 请求,parent字段应包含父级相对资源名 |
create_time | Timestamp | 一个实体的创建时间戳 |
update_time | Timestamp | 一个实体的最后更新时间戳;注意update_time会被create/patch/delete等操作更新 |
delete_time | Timestamp | 实体的删除时间戳,仅当支持保留时。 |
time_zone | string | 时区名,它应该符合IANA时区标准,如”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 | 应用于Search 方法的(也就是说 :search )过滤参数 |
page_token | string | List 请求的数据分页令牌 |
page_size | int32 | List 请求的数据分页大小 |
total_size | int32 | 列表中的总条目数,不考虑分页 |
next_page_token | string | List 返回结果中下一个分页的令牌。它应该在后续请求中传递为page_token参数;空值意味着没有更多数据 |
request_id | string | 用于检测重复请求的唯一字符串id |
resume_token | string | 用于恢复流式传输请求的隐含令牌 |
labels | map\ | 表示云资源的标签 |
deleted | bool | 如果资源允许取消删除,则它必须有deleted字段表示资源是否已被删除 |
show_deleted | bool | 如果资源允许取消删除,相应的List 方法必须有一个show_deleted字段,以便客户端发现已删除的资源。 |
validate_only | bool | 如果为true,则表示给定的请求仅需要被检验,而不是被执行。 |
错误
当API方式错误时,返回一个错误状态给客户端:
message Status {
// A simple error code that can be easily handled by the client.
int32 code = 1;
// A developer-facing human-readable error message. 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;
}
错误代码应统一定义。
错误消息
错误消息应帮助用户方便、快速地理解和解决API错误,编写错误消息时,一般应遵循:
- 不要假定用户是API专家:用户可能是客户端开发人员、操作人员、IT人员或程序最终用户;
- 不要假定用户了解服务实现或错误上下文;
- 若可能,应构造错误消息,以便技术用户可以响应错误并更正;
- 保持错误消息的简洁:需要时可提供链接,帮助用户反馈或获取更新信息。
HTTP映射
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.status |
details | 这对应于Status.details |
状态码
常见错误码与其原因简短说明:
HTTP | RPC | 描述 |
---|---|---|
200 | OK | 没有错误 |
400 | INVALID_ARGUMENT | 客户端指定了无效的参数。 检查错误消息和错误详细信息以获取更多信息。 |
400 | FAILED_PRECONDITION | 请求不能在当前系统状态下执行,例如删除非空目录。 |
400 | OUT_OF_RANGE | 客户端指定了无效的范围。 |
401 | UNAUTHENTICATED | 由于遗失,无效或过期的OAuth令牌而导致请求未通过身份验证。 |
403 | PERMISSION_DENIED | 客户端没有足够的权限。这可能是因为OAuth令牌没有正确的范围,客户端没有权限,或者客户端项目尚未启用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 | 已超过请求期限。如果重复发生,请考虑降低请求的复杂性。 |
命名约定
为提供一致的开发体验,所有命名都应:
- 简单:为简化可使用广泛接受的短语或缩写;
- 直观:尽量使用直观、熟悉的术语;
- 一致:使用正确的英语;同一概念使用相同的名称,不同概念使用不同名称;
命名一致性示例:
API 名称 | 示例 |
---|---|
产品名称 | Google Calendar API |
服务名称 | calendar.googleapis.com |
包名称 | google.calendar.v3 |
接口名称 | google.calendar.v3.CalendarService |
源代码目录 | //google/calendar/v3 |
API 名称 | calendar |
服务名称
服务名称应当符合DNS命名的语法规范,确保它可以被解析成一个或多个网络地址。
如果一个API是由多个服务组成,那么他们的命名应当更容易被发现:一种方法是所有服务名称共享一个公共前缀。
包名称
包名称应当与产品名称和服务名称保持一致,同时必须包含版本信息:
// Google Calendar API
package google.calendar.v3;
方法名称
方法名称应当使用首字母小写驼峰体的名词:
动词 | 名词 | 方法名称 | 请求 | 响应 |
---|---|---|---|---|
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 |