AIP-134

Standard methods: Update

In REST APIs, it is customary to make a PATCH or PUT request to a resource’s URI (for example, /v1/publishers/{publisher}/books/{book}) in order to update that resource.

Resource-oriented design (AIP-121) honors this pattern through the Update method (which mirrors the REST PATCH behavior). These RPCs accept the URI representing that resource and return the resource.

Guidance

APIs should generally provide an update method for resources unless it is not valuable for users to do so. The purpose of the update method is to make changes to the resources without causing side effects.

Update methods are specified using the following pattern:

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";
}
  • The RPC’s name must begin with the word Update. The remainder of the RPC name should be the singular form of the resource’s message name.
  • The request message must match the RPC name, with a -Request suffix.
  • The response message must be the resource itself. (There is no UpdateBookResponse.)
    • If the update RPC is long-running, the response message must be a google.longrunning.Operation which resolves to the resource itself.
  • The method should support partial resource update, and the HTTP verb should be PATCH.
    • If the method will only ever support full resource replacement, then the HTTP verb may be PUT. However, this is strongly discouraged because it becomes a backwards-incompatible change to add fields to the resource.
  • The resource’s name field should map to the URI path.
    • The {resource}.name field should be the only variable in the URI path.
  • There must be a body key in the google.api.http annotation, and it must map to the resource field in the request message.
    • All remaining fields should map to URI query parameters.
  • There should be exactly one google.api.method_signature annotation, with a value of "{resource},update_mask".

Note: Unlike the other four standard methods, the URI path here references a nested field (book.name) in the example. If the resource field has a word separator, snake_case is used.

Request message

Update methods implement a common request message pattern:

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;
}
  • The request message must contain a field for the resource.
    • The field must map to the PATCH body.
    • The field should be annotated as required.
    • A name field must be included in the resource message. It should be called name.
  • A field mask should be included in order to support partial update. It must be of type google.protobuf.FieldMask, and it should be called update_mask.
    • The fields used in the field mask correspond to the resource being updated (not the request message).
    • The field may be required or optional. If it is required, it should include the corresponding annotation.
  • The request message must not contain any other required fields, and should not contain other optional fields except those described in this or another AIP.

Side effects

In general, update methods are intended to update the data within the resource. Update methods should not trigger other side effects. Instead, side effects should be triggered by custom methods.

In particular, this entails that state fields must not be directly writable in update methods.

PATCH and PUT

TL;DR: Google APIs generally use the PATCH HTTP verb only, and do not support PUT requests.

We standardize on PATCH because Google updates stable APIs in place with backwards-compatible improvements. It is often necessary to add a new field to an existing resource, but this becomes a breaking change when using PUT.

To illustrate this, consider a PUT request to a Book resource:

PUT /v1/publishers/123/books/456

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

Next consider that the resource is later augmented with a new field (here we add rating):

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

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

If a rating is set on a book and the existing PUT request was executed, it would wipe out the book’s rating. In essence, a PUT request unintentionally wiped out data because the previous version did not know about it.

Long-running update

Some resources take longer to update a resource than is reasonable for a regular API request. In this situation, the API should use a long-running operation (AIP-151) instead:

rpc UpdateBook(UpdateBookRequest) returns (google.longrunning.Operation) {
  option (google.api.http) = {
    patch: "/v1/{book.name=publishers/*/books/*}"
  };
  option (google.longrunning.operation_info) = {
    response_type: "Book"
    metadata_type: "OperationMetadata"
  }
}
  • The response type must be set to the resource (what the return type would be if the RPC was not long-running).
  • Both the response_type and metadata_type fields must be specified.

Create or update

If the API accepts client-assigned resource names, the server may allow the client to specify a non-existent resource name and create a new resource (effectively implementing the “upsert” concept). Otherwise, the Update method should fail with NOT_FOUND.

An API with an update method that supports resource creation should also provide a create method, because otherwise it may not be clear to users how to create resources.

Etags

An API may sometimes need to allow users to send update requests which are guaranteed to be made against the most current data (a common use case for this is to detect and avoid race conditions). Resources which need to enable this do so by including a string etag field, which contains an opaque, server-computed value representing the content of the resource.

In this situation, the resource should contain a string etag field:

message Book {
  // The resource name of the book.
  // Format: publishers/{publisher}/books/{book}
  string name = 1;

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

The etag field may be either required or optional. If it is set, then the request must succeed if and only if the provided etag matches the server-computed value, and must fail with a FAILED_PRECONDITION error otherwise. The update_mask field in the request does not affect the behavior of the etag field, as it is not a field being updated.

Expensive fields

APIs sometimes encounter situations where some fields on a resource are expensive or impossible to reliably return.

This can happen in a few situations:

  • A resource may have some fields that are very expensive to compute, and that are generally not useful to the customer on update requests.
  • A single resource sometimes represents an amalgamation of data from multiple underlying (and eventually consistent) data sources. In these situations, it is impossible to return authoritative information on the fields that were not changed.

In this situation, an API may return back only the fields that were updated, and omit the rest, and should document this behavior if they do so.

Changelog

  • 2019-10-18: Added guidance on annotations.
  • 2019-09-10: Added a link to the long-running operations AIP (AIP-151).
  • 2019-08-01: Changed the examples from “shelves” to “publishers”, to present a better example of resource ownership.
  • 2019-06-10: Added guidance for long-running update.
  • 2019-05-29: Added an explicit prohibition on arbitrary fields in standard methods.