tommwq.work/aip

AIP-158 分页

· [tommwq@126.com]
编号 158
原文链接 https://google.aip.dev/158
状态 批准
创建日期 2019-02-18
更新日期 2019-02-18

API通常需要提供数据集,最常见的是 List 标准方法。但集合大小往往是不受控制的,会随着时间增长,提高了查找时间和通过网络传输的应答大小。因此对集合进行分页是非常重要的。

指南

返回数据集的接口 必须 一开始就提供分页功能,因为向现存方法添加分页功能是无法向后兼容的变更

// The request structure for listing books.
message ListBooksRequest {
  // The parent, which owns this collection of books.
  // Format: publishers/{publisher}
  string parent = 1 [
    (google.api.field_behavior) = REQUIRED,
    (google.api.resource_reference) = {
      child_type: "library.googleapis.com/Book"
    }];

  // The maximum number of books to return. The service may return fewer than
  // this value.
  // If unspecified, at most 50 books will be returned.
  // The maximum value is 1000; values above 1000 will be coerced to 1000.
  int32 page_size = 2;

  // A page token, received from a previous `ListBooks` call.
  // Provide this to retrieve the subsequent page.
  //
  // When paginating, all other parameters provided to `ListBooks` must match
  // the call that provided the page token.
  string page_token = 3;
}

// The response structure from listing books.
message ListBooksResponse {
  // The books from the specified publisher.
  repeated Book books = 1;

  // A token that can be sent as `page_token` to retrieve the next page.
  // If this field is omitted, there are no subsequent pages.
  string next_page_token = 2;
}
  • 返回集合的请求消息 应当 定义 int32 page_size 域,允许用户设定返回的最大结果数量。
    • page_size不得 是必需域。
    • 如果用户未设定 page_size (或设定为 0 ),API选择合适的默认值。API 应当 在文档中记录默认值。API 不得 返回错误。
    • 如果用户设定的 page_size 大于 API 允许的最大值,API 应当 将其强制设置为允许的最大页大小。
    • 如果用户为 page_size 设定了负值,API 必须 返回 INVALID_ARGUMENT 错误。
    • API 可以 返回少于请求数量(包括零)的结果,即使未到达集合末尾。
  • 集合的请求消息 应当 定义 string page_token 域,允许用户前进到集合的下一页。
    • page_token不得 是必需域。
    • 如果用户在后续分页请求中修改了 page_size ,服务 必须 遵守新的页大小。
    • 用户要保持接口的所有其他参数不变;如果任何参数不同,API 应当 返回 INVALID_ARGUMENT 错误。
  • 应答 不得 是流式应答。
  • 返回集合的应答消息 应当 定义 string next_page_token 域,为用户提供用于检索下一页的页令牌。
    • 包含分页结果的域 应当 是消息的第一个域,编号为 1 。它 应当 是重复域,包含一组资源,构成一个结果页。
    • 如果已到达集合末尾, next_page_token必须 为空。这是告诉用户“集合结束”的 唯一 方法。
    • 如果未到达集合末尾(或API无法及时确认),API 必须 提供 next_page_token
  • 返回集合的应答消息 可以 提供 int32 total_size 域,为用户提供列表总项目数。
    • 总数 可以 是估计值(此时API 应当 在文档中明确记录)。

跳过部分结果

分页操作请求 可以 定义一个 int32 skip 域,允许用户跳过部分结果。

skip必须 表示要待跳过的单个资源数量,而非页数量。

例如:

  • 没有页令牌,且 skip 值为 30 的请求,返回从第 31 个结果开始的单个页。
  • 带有对应第51个结果的页令牌(因为前50个结果已在第1页返回),且 skip 值为 30 的请求,返回从第 81 个结果开始的单个页。

如果提供的 skip 值使得游标移动到结果集末尾之后,应答 必须200 OK ,且结果集为空,不再提供 next_page_token

不透明

API 提供的页令牌 必须 是不透明(符合URL要求)的字符串, 不得 让用户能够解析。如果用户能解析令牌, 他们真的会这么做 。这实际上让 API 分页的实现细节成为 API 界面的一部分,并且无法在不影响用户的情况下修改实现细节。

警告 对透明的页令牌进行 Base-64 编码 不是 有效的混淆机制。

如果页令牌不需要存储在数据库中,也不包含敏感数据,API 可以 定义一个包含所需数据的内部protocol buffer消息,发送消息的序列化结果(base-64 编码)作为混淆页令牌。

页令牌 必须 只用于提供分页过程继续位置的指示。 不得 提供对底层资源的任何形式的授权,授权 必须 像任何其他请求一样执行,无论是否存在页令牌。

页令牌失效

许多 API 在内部将页令牌存储在数据库中。此时API 可以 在发送页令牌后,经过一段合理时间,使页令牌失效,避免额外存储大量几乎无效的数据。这种行为不需要记录到文档中。

注意 不同API的合理时间可能不同,一个好的经验法则是3天。

向后兼容

向现存接口添加分页特性是无法向后兼容的变更。乍看很奇怪;向 proto 消息添加域通常是向后兼容的。然而添加分页在 行为上 是不兼容的。

考虑一个用户,集合中有 75 个资源。他已经编写并部署了代码。如果 API 后续添加分页域,将默认值设置为 50,他的代码将失效:代码原本取得所有资源,现在只取得前 50 个(并且代码还不知道如何进行分页)。即使 API 设置了很高的默认限制,例如 100,用户的集合可能会增长, 最后 代码还会失效。

此外,客户端库实现了自动分页,通常使用不同的方法签名区分分页接口和非分页接口。这意味着向未分页方法添加分页,将导致这些库产生破坏性更改。

因此,始终在返回集合的接口中 预先 添加分页很重要。真的很重要。以后再添加分页,无法避免不给现有用户带来麻烦。

警告 这也意味着,除了展示分页域外,接口的 实际实现 必须 使用有限的默认值。对于最初较小的集合,实现一个内存版本(取得的所有内容之后再分页)是合适的。

修订记录

  • 2020-05-24 明确指出添加分页会破坏客户端库。
  • 2020-05-13 添加跳过部分结果指导原则。
  • 2020-08-24 明确指出应答不是流式应答。
  • 2020-06-24 明确指出页大小对用户始终是可选的。
  • 2019-02-12 添加关于分页域的指导原则。
  • 2019-08-01 将示例从 “shelves” 改为 “publishers”,提供更好的资源所有权示例。
  • 2019-07-19 将不透明性要求从“应该”改为“必须”。