tommwq.work/aip

AIP-134 标准方法:Update

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

REST API通常向资源URI(如 /v1/publishers/{publisher}/books/{book} )发出 PATCHPUT 请求,更新资源。

面向资源设计(AIP-121)提供Update方法(类似REST PATCH行为),遵循这一模式。这些接口接受代表资源的URI,返回资源。

指南

API通常 应当 为资源提供Update方法,除非对用户没实际意义。Update方法的目的是修改资源,而不引发副作用。

Update方法使用以下模式:

rpc UpdateBook(UpdateBookRequest) returns (Book) {
  option (google.api.http) = {
    patch: "/v1/{book.name=publishers/*/books/*}"
    body: "book"
  };
  option (google.api.method_signature) = "book,update_mask";
}
  • 接口名字 必须 以单词Update开头,其余部分 应当 是资源消息名字的单数形式。
  • 请求消息 必须 与接口名字一致,并带有 Request 后缀。
  • 应答消息必须是资源本身。(不存在 UpdateBookResponse 。)
    • 应答 应当 包含完整资源数据, 必须 包含客户端发送的全部域,并在域掩码中包含这些域。除非它们是只输入域(参考AIP-203)。
    • 如果Update接口是耗时更新,应答消息 必须 google.longrunning.Operation ,解析为资源自身。
  • 方法 应当 支持更新部分资源,对应HTTP动词是 PATCH
    • 如果方法仅支持完全资源替换,HTTP动词 可以PUT 。然而非常不建议这样做,这导致向资源添加新域的变更无法向后兼容。
  • 资源的 name应当 映射到URI路径。
    • {resource}.name应当 是URI路径中唯一的变量。
  • google.api.http 注解中 必须 包含 body 键, 必须 映射到请求消息中的资源域。
    • 所有其他域 应当 映射到URI查询参数。
  • 应当 只有一个 google.api.method_signature 注解,值为 "{resource},update_mask"
  • 如果API在管理平面上运行,操作应该具有强一致性:更新操作完成时,所有用户可设定值和资源的存在性 必须 达到稳定状态,读取资源状态将返回一致的应答。

注意 与其他四个标准方法不同,这里的URI路径引用了示例中的嵌套域( book.name )。如果资源域存在单词分隔符,使用 snake_case

请求消息

Update方法实现了常见的请求消息模式:

message UpdateBookRequest {
  // The book to update.
  //
  // The book's `name` field is used to identify the book to update.
  // Format: publishers/{publisher}/books/{book}
  Book book = 1 [(google.api.field_behavior) = REQUIRED];

  // The list of fields to update.
  google.protobuf.FieldMask update_mask = 2;
}
  • 请求消息 必须 包含表示资源的域。
    • 必须 映射到 PATCH 主体。
    • 应当 注释为必需域
    • 资源消息 必须 包含 name 域。
    • name必须 标识需要更新的资源的类型
  • 如果允许更新部分资源, 必须 包含域掩码。 必须google.protobuf.FieldMask 类型, 必须 称为 update_mask
    • 域掩码中包含的域对应于欲更新资源(而非请求消息)。
    • 必须 是可选的,服务 必须 将缺失的域掩码视为等同于包含了所有域的域掩码。
    • 更新掩码 必须 支持特殊值 * ,表示完全替换资源(等同于 PUT )。
      • API生产者在向资源添加新的可变域时,需要注意消费者在不知道新可变域的情况下是如何处理通配符( * )的。同样地,消费者只有风险可控的条件下才使用通配符。通常而言,最安全的做法是明确指定要更新的域,而非通配符( * )。
  • 请求消息 不得 包含任何其他必需域, 不应 包含其他可选域,除非本AIP或其他AIP有要求。

副作用

通常,Update方法的目标是更新资源中的数据。Update方法 不应 引发其他副作用。相反,副作用 应当 通过自定义方法触发。

特别的,这意味着在Update方法中,状态域不可以直接修改。

PATCHPUT

概要: Google API通常只使用HTTP动词 PATCH ,不支持 PUT 请求。

Google统一使用 PATCH ,因为在变更稳定API时提升了向后兼容性。常常需要向现有资源添加新域,但在使用 PUT 时,这会成为破坏性变更。

为了说明这一点,考虑对 Book 资源的 PUT 请求:

PUT /v1/publishers/123/books/456

{"title": "Mary Poppins", "author": "P.L. Travers"}

接下来考虑资源后来增加了一个新域(这里添加了 rating ):

message Book {
  string title = 1;
  string author = 2;

  // Subsequently added to v1 in place...
  int32 rating = 3;
}

如果一个图书资源设置了评分,然后执行了旧版本的 PUT 请求,将清除图书评分信息。本质上 PUT 请求无意中清除了数据,因为旧版本不知道这个域。

耗时更新

某些资源在更新时需要的时间比常规API应答时间更长。此时API应使用耗时操作(AIP-151)模式:

rpc UpdateBook(UpdateBookRequest) returns (google.longrunning.Operation) {
  option (google.api.http) = {
    patch: "/v1/{book.name=publishers/*/books/*}"
    body: "book"
  };
  option (google.longrunning.operation_info) = {
    response_type: "Book"
    metadata_type: "OperationMetadata"
  };
}
  • 应答类型 必须 设置为资源(如果接口不是耗时操作,返回类型会是什么)。
  • 必须 设定 response_typemetadata_type 域。

注意 声明友好资源(AIP-128应当 使用耗时更新。

Create或Update

如果服务使用客户端分配的资源名字,Update方法 可以 提供 bool allow_missing 域,让方法允许用户更新一个不存在的资源(在处理过程中创建资源):

message UpdateBookRequest {
  // The book to update.
  //
  // The book's `name` field is used to identify the book to be updated.
  // Format: publishers/{publisher}/books/{book}
  Book book = 1 [(google.api.field_behavior) = REQUIRED];

  // The list of fields to be updated.
  google.protobuf.FieldMask update_mask = 2;

  // If set to true, and the book is not found, a new book will be created.
  // In this situation, `update_mask` is ignored.
  bool allow_missing = 3;
}

更具体地说, allow_missing 标志引发以下行为:

  • 如果方法在一个不存在的资源上调用,创建资源。设定所有请求中包含的域,无论是否出现在域掩码中。
    • 如果缺少必需域或域值无效,返回 INVALID_ARGUMENT 错误。
  • 如果方法在已存在的资源上调用,并且所有域值一致,返回未修改的现存资源。
  • 如果方法在已存在的资源上调用,只更新域掩码中声明的域。

用户 必须 拥有更新权限才能调用Update,即使将 allow_missing 设置为 true 。对于希望防止用户通过Update方法创建资源的API, 应当 使用IAM(身份和访问管理)条件。

etag

API有时需要允许用户发送Update请求,请求需要保证应用到最新数据上(常见的使用场景是为了检测和避免竞态条件)。需要启用这个功能的资源要包含 string etag 域,包含一个由服务器计算的、代表资源内容的密值。

此时资源 应当 包含 string etag 域:

message Book {
  option (google.api.resource) = {
    type: "library.googleapis.com/Book"
    pattern: "publishers/{publisher}/books/{book}"
  };

  // The resource name of the book.
  // Format: publishers/{publisher}/books/{book}
  string name = 1 [(google.api.field_behavior) = IDENTIFIER];

  // The title of the book.
  // Example: "Mary Poppins"
  string title = 2;

  // The author of the book.
  // Example: "P.L. Travers"
  string author = 3;

  // The etag for this book.
  // If this is provided on update, it must match the server's etag.
  string etag = 4;
}

etag可以必需域可选域。如果设定了 etag 域,只有当值与服务器计算结果匹配时,请求才能成功。否则 必须 返回 ABORTED 错误。请求中的 update_mask 域不会影响 etag 域的行为,因为不是一个 需要 更新的域。

高成本域

API有时会遇到这种情况:资源中某些域的计算成本很高,或者根本无法返回可靠值。

这可能发生在以下几种场景:

  • 资源中可能有一些域的计算成本非常高,并且通常在Update请求中对客户没用。
  • 单一资源有时用来表示来自多个底层(最终一致的)数据源的数据的汇总。此时无法返回那些未修改域的准确信息。

在这种情况下,API 可以 只返回已更新域,省略其他。如果这样做, 应当 用文档记录这种行为。

错误

参考错误,特别是何时使用PERMISSION_DENIED和NOT_FOUND错误

此外,如果用户确实拥有适当权限,但请求的资源不存在,服务 必须 返回 NOT_FOUND (HTTP 404)错误, allow_missing 设定为 true 时除外。

修订记录

  • 2024-12-03 添加使用完整替换域掩码的注意事项。
  • 2024-03-14update_mask 域的可选行为指南改为 必须
  • 2023-08-26 添加一致性要求。
  • 2023-07-17update_mask 域名字指南改为 必须
  • 2022-11-04 将错误指南汇总到AIP-193
  • 2022-06-02 修改后缀描述,消除多余的“-”。
  • 2021-11-04 修改设定 allow_missing 时的权限检查。
  • 2021-07-08 添加资源未找到场景的错误指南。
  • 2021-03-05etag 错误从 FAILED_PRECONDITION (即HTTP 400)改为 ABORTED (409)。
  • 2020-10-06 添加声明友好资源指南。
  • 2020-10-06 添加 allow_missing 指南。
  • 2020-08-14 添加权限拒绝场景错误指南。
  • 2020-06-08 添加返回完整资源数据的指南。
  • 2019-10-18 添加注释指南。
  • 2019-09-10 添加耗时操作AIP链接(AIP-151)。
  • 2019-08-01 将示例从“shelves”改为“publishers”,提供更好的资源所有权示例。
  • 2019-06-10 添加耗时更新指南。
  • 2019-05-29 添加禁止在标准方法中使用任意域的明确禁令。