Skip to content

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 (AEP-121) honors this pattern through the Update method (which mirrors the REST PATCH behavior). These methods accept the URI representing that resource and return the resource.

Also see the apply method, with guidance on how to implement PUT requests.

Guidance

APIs should 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.

Operation

  • The method should support partial resource update, and the HTTP verb should be PATCH.
  • The operation must have strong consistency.
  • Some resources take longer to be created than is reasonable for a regular API request. In this situation, the API should use a long-running operation.
  • The response schema must be the resource itself.
    • The response should include the fully-populated resource, and must include any fields that were sent and included in the update mask unless they are input only (see AEP-203).
rpc UpdateBook ( UpdateBookRequest ) returns ( Book ) {
option (google.api.http) = {
patch: "/{path=publishers/*/books/*}",
body: "book"
};
option (google.api.method_signature) = "book,update_mask";
}

Update methods are specified using the following pattern:

  • The method’s name must begin with the word Update. The remainder of the method name must be the singular form of the resource’s name.

  • The request schema’s name must exactly match the RPC name, with a Request suffix.

  • The request’s path field must map to the URI path.

    • The path field must 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".

Requests

Update methods implement a common request pattern:

  • The request must contain a field for the resource.
    • The name of this field must be the singular form of the resource’s name.
  • The request must not contain any required fields other than those described in this section, and should not contain other optional fields except those described in this or another AEP.
message UpdateBookRequest {
// The globally unique identifier for the resource
string path = 10018 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = { type: "bookstore.example.com/book" }
];
// The resource to perform the operation on.
Book book = 10015 [(google.api.field_behavior) = REQUIRED];
// The update mask for the resource
google.protobuf.FieldMask update_mask = 10012;
}
  • A path field must be included.
  • The request message field for the resource must map to the PATCH body.
  • The request message field for the resource should be annotated as required.
    • The field must identify the resource type of the resource being updated.
  • If partial resource update is supported, a field mask must be included. It must be of type google.protobuf.FieldMask, and it must 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 must include the corresponding annotation. If optional, the service must treat an omitted field mask as an implied field mask equivalent to all fields that are populated (have a non-empty value).

    • Update masks must support a special value *, meaning full replacement (the equivalent of PUT).

Responses

message Book {
option (google.api.resource) = {
type: "bookstore.example.com/book",
pattern: [ "publishers/{publisher}/books/{book}" ],
plural: "books",
singular: "book"
};
// A Author.
message Author {
// Field for firstName.
string firstName = 1;
// Field for lastName.
string lastName = 2;
}
// Field for author.
repeated Author author = 5;
// Field for isbn.
repeated string isbn = 1 [(google.api.field_behavior) = REQUIRED];
// Field for price.
float price = 2 [(google.api.field_behavior) = REQUIRED];
// Field for published.
bool published = 3 [(google.api.field_behavior) = REQUIRED];
// Field for edition.
int32 edition = 4;
// Field for path.
string path = 10000;
}

Errors

See errors, in particular when to use PERMISSION_DENIED and NOT_FOUND errors.

In addition, if the user does have proper permission, but the requested resource does not exist, the service must error with NOT_FOUND (HTTP 404) unless allow_missing is set to true.

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

We standardize on PATCH because many organizations update 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, and use a protobuf example without loss of generality):

message Book {
string title = 1;
string author = 2;
// Subsequently added to v1 in place...
int32 rating = 3;
}

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

FieldMasks in proto and json merge-patch in HTTP

AEP recommends a specific diverence in behavior between the proto and the HTTP interfaces. Specifically:

  • The inclusion of the update_mask in the proto variant, requiring the user to explicitly specify fields to be updated.
  • The usage of IETF RFC 7396 - Json Merge Patch for HTTP APIs.

This divergence in behavior is intentional, and exists for the following reasons:

  1. The update mask is a proto-specific concept, due to the lack of ability across all proto versions to differentiate if the user has explicitly populated a field or not. JSON has the ability to express whether a field is present (by omitting it from the JSON payload). Ultimately, this allows the field mask + proto pair and json to be translatable.
  2. RFC 7396 is a popular and well-understood standard for HTTP. Introducing a new standard for HTTP would have made the AEP HTTP variant less idiomatic.
  3. For HTTP-proto bindings, there is a way to generate the proto field mask from the fields present in the JSON request. This is what is recommended in the API Design Patterns book, section 8.2 , describing the Google AIPs from which AEP-134 is forked. Implementations of gateway-grpc proto bindings such as gateway-grpc support this translation.

Therefore, given the ability of these two different patch mechanisms to interoperate while maintaining idiomatic practices, this divergence was concluded to be the least worst option.

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 {
option (google.api.resource) = {
type: "library.example.com/Book"
pattern: "publishers/{publisher}/books/{book}"
};
// The resource path of the book.
// Format: publishers/{publisher}/books/{book}
string path = 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;
}

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 an ABORTED 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 may be infeasible 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. APIs that do this must document this behavior.

Interface Definitions

rpc UpdateBook ( UpdateBookRequest ) returns ( Book ) {
option (google.api.http) = {
patch: "/{path=publishers/*/books/*}",
body: "book"
};
option (google.api.method_signature) = "book,update_mask";
}
message UpdateBookRequest {
// The globally unique identifier for the resource
string path = 10018 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = { type: "bookstore.example.com/book" }
];
// The resource to perform the operation on.
Book book = 10015 [(google.api.field_behavior) = REQUIRED];
// The update mask for the resource
google.protobuf.FieldMask update_mask = 10012;
}
message Book {
option (google.api.resource) = {
type: "bookstore.example.com/book",
pattern: [ "publishers/{publisher}/books/{book}" ],
plural: "books",
singular: "book"
};
// A Author.
message Author {
// Field for firstName.
string firstName = 1;
// Field for lastName.
string lastName = 2;
}
// Field for author.
repeated Author author = 5;
// Field for isbn.
repeated string isbn = 1 [(google.api.field_behavior) = REQUIRED];
// Field for price.
float price = 2 [(google.api.field_behavior) = REQUIRED];
// Field for published.
bool published = 3 [(google.api.field_behavior) = REQUIRED];
// Field for edition.
int32 edition = 4;
// Field for path.
string path = 10000;
}