|
| 1 | +# Update |
| 2 | + |
| 3 | +In REST APIs, it is customary to make a `PATCH` or `PUT` request to a |
| 4 | +resource's URI (for example, `/v1/publishers/{publisher}/books/{book}`) in |
| 5 | +order to update that resource. |
| 6 | + |
| 7 | +Resource-oriented design (AEP-121) honors this pattern through the `Update` |
| 8 | +method (which mirrors the REST `PATCH` behavior). These methods accept the URI |
| 9 | +representing that resource and return the resource. |
| 10 | + |
| 11 | +## Guidance |
| 12 | + |
| 13 | +APIs **should** provide an update method for resources unless it is not |
| 14 | +valuable for users to do so. The purpose of the update method is to make |
| 15 | +changes to the resources without causing side effects. |
| 16 | + |
| 17 | +Update methods are specified using the following pattern: |
| 18 | + |
| 19 | +- The method's name **must** begin with the word `Update`. The remainder of the |
| 20 | + method name **should** be the singular form of the resource's name. |
| 21 | +- The request schema's name **must** exactly match the RPC name, with a |
| 22 | + `Request` suffix. |
| 23 | +- The response schema **must** be the resource itself. |
| 24 | + - The response **should** include the fully-populated resource, and **must** |
| 25 | + include any fields that were sent and included in the update mask unless |
| 26 | + they are input only (see AEP-203). |
| 27 | + - If the update RPC is [long-running](#long-running-update), the response |
| 28 | + **must** be an `Operation` for which the return type is the resource |
| 29 | + itself. |
| 30 | +- The method **should** support partial resource update, and the HTTP verb |
| 31 | + **should** be `PATCH`. |
| 32 | + |
| 33 | +{% tab proto %} |
| 34 | + |
| 35 | +```proto |
| 36 | +rpc UpdateBook(UpdateBookRequest) returns (Book) { |
| 37 | + option (google.api.http) = { |
| 38 | + patch: "/v1/{book.path=publishers/*/books/*}" |
| 39 | + body: "book" |
| 40 | + }; |
| 41 | + option (google.api.method_signature) = "book,update_mask"; |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +- The resource's `path` field **should** map to the URI path. |
| 46 | + - The `{resource}.path` field **should** be the only variable in the URI |
| 47 | + path. |
| 48 | +- There **must** be a `body` key in the `google.api.http` annotation, and it |
| 49 | + **must** map to the resource field in the request message. |
| 50 | + - All remaining fields **should** map to URI query parameters. |
| 51 | +- There **should** be exactly one `google.api.method_signature` annotation, |
| 52 | + with a value of `"{resource},update_mask"`. |
| 53 | + |
| 54 | +**Note:** Unlike the other four standard methods, the URI path here references |
| 55 | +a nested field (`book.path`) in the example. If the resource field has a word |
| 56 | +separator, `snake_case` is used. |
| 57 | + |
| 58 | +{% tab oas %} |
| 59 | + |
| 60 | +**Note:** OAS example not yet written. |
| 61 | + |
| 62 | +{% endtabs %} |
| 63 | + |
| 64 | +### Request schema |
| 65 | + |
| 66 | +Update methods implement a common request pattern: |
| 67 | + |
| 68 | +- The request **must** contain a field for the resource. |
| 69 | + - The name of this field **must** be the singular form of the resource's |
| 70 | + name. |
| 71 | +- The request **must not** contain any required fields other than those |
| 72 | + described in this section, and **should not** contain other optional fields |
| 73 | + except those described in this or another AEP. |
| 74 | + |
| 75 | +{% tab proto %} |
| 76 | + |
| 77 | +```proto |
| 78 | +message UpdateBookRequest { |
| 79 | + // The book to update. |
| 80 | + // |
| 81 | + // The book's `path` field is used to identify the book to update. |
| 82 | + // Format: publishers/{publisher}/books/{book} |
| 83 | + Book book = 1 [(google.api.field_behavior) = REQUIRED]; |
| 84 | + |
| 85 | + // The list of fields to update. |
| 86 | + google.protobuf.FieldMask update_mask = 2; |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +- The request message field for the resource **must** map to the `PATCH` body. |
| 91 | +- The request message field for the resource **should** be [annotated as |
| 92 | + required][aep-203]. |
| 93 | + - The field **must** identify the [resource type][aep-123] of the resource |
| 94 | + being updated. |
| 95 | +- If partial resource update is supported, a field mask **must** be included. |
| 96 | + It **must** be of type `google.protobuf.FieldMask`, and it **must** be called |
| 97 | + `update_mask`. |
| 98 | + - The fields used in the field mask correspond to the resource being updated |
| 99 | + (not the request message). |
| 100 | + - The field **may** be required or optional. If it is required, it **must** |
| 101 | + include the corresponding annotation. If optional, the service **must** |
| 102 | + treat an omitted field mask as an implied field mask equivalent to all |
| 103 | + fields that are populated (have a non-empty value). |
| 104 | + - Update masks **must** support a special value `*`, meaning full replacement |
| 105 | + (the equivalent of `PUT`). |
| 106 | + |
| 107 | +{% tab oas %} |
| 108 | + |
| 109 | +**Note:** OAS example not yet written. |
| 110 | + |
| 111 | +{% endtabs %} |
| 112 | + |
| 113 | +### Side effects |
| 114 | + |
| 115 | +In general, update methods are intended to update the data within the resource. |
| 116 | +Update methods **should not** trigger other side effects. Instead, side effects |
| 117 | +**should** be triggered by custom methods. |
| 118 | + |
| 119 | +In particular, this entails that [state fields][] **must not** be directly |
| 120 | +writable in update methods. |
| 121 | + |
| 122 | +### PATCH and PUT |
| 123 | + |
| 124 | +**TL;DR:** AEP-compliant APIs generally use the `PATCH` HTTP verb only, and do |
| 125 | +not support `PUT` requests. |
| 126 | + |
| 127 | +We standardize on `PATCH` because many organizations update stable APIs in |
| 128 | +place with backwards-compatible improvements. It is often necessary to add a |
| 129 | +new field to an existing resource, but this becomes a breaking change when |
| 130 | +using `PUT`. |
| 131 | + |
| 132 | +To illustrate this, consider a `PUT` request to a `Book` resource: |
| 133 | + |
| 134 | + PUT /v1/publishers/123/books/456 |
| 135 | + |
| 136 | + {"title": "Mary Poppins", "author": "P.L. Travers"} |
| 137 | + |
| 138 | +Next consider that the resource is later augmented with a new field (here we |
| 139 | +add `rating`, and use a protobuf example without loss of generality): |
| 140 | + |
| 141 | +```proto |
| 142 | +message Book { |
| 143 | + string title = 1; |
| 144 | + string author = 2; |
| 145 | + |
| 146 | + // Subsequently added to v1 in place... |
| 147 | + int32 rating = 3; |
| 148 | +} |
| 149 | +``` |
| 150 | + |
| 151 | +If a rating were set on a book and the existing `PUT` request were executed, it |
| 152 | +would wipe out the book's rating. In essence, a `PUT` request unintentionally |
| 153 | +would wipe out data because the previous version did not know about it. |
| 154 | + |
| 155 | +### Long-running update |
| 156 | + |
| 157 | +Some resources take longer to update a resource than is reasonable for a |
| 158 | +regular API request. In this situation, the API **should** use a long-running |
| 159 | +operation (AIP-151) instead: |
| 160 | + |
| 161 | +- The response type **must** be set to the resource (what the return type would |
| 162 | + be if the method were not long-running). |
| 163 | + |
| 164 | +{% tab proto %} |
| 165 | + |
| 166 | +```proto |
| 167 | +rpc UpdateBook(UpdateBookRequest) returns (aep.api.Operation) { |
| 168 | + option (google.api.http) = { |
| 169 | + patch: "/v1/{book.name=publishers/*/books/*}" |
| 170 | + }; |
| 171 | + option (aep.api.operation_info) = { |
| 172 | + response_type: "Book" |
| 173 | + metadata_type: "OperationMetadata" |
| 174 | + }; |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +- Both the `response_type` and `metadata_type` fields **must** be specified. |
| 179 | + |
| 180 | +{% tab oas %} |
| 181 | + |
| 182 | +**Note:** OAS example not yet written. |
| 183 | + |
| 184 | +{% endtabs %} |
| 185 | + |
| 186 | +### Create or update |
| 187 | + |
| 188 | +If the service uses client-assigned resource paths, `Update` methods **may** |
| 189 | +expose a `bool allow_missing` field, which will cause the method to succeed in |
| 190 | +the event that the user attempts to update a resource that is not present (and |
| 191 | +will create the resource in the process): |
| 192 | + |
| 193 | +{% tab proto %} |
| 194 | + |
| 195 | +```proto |
| 196 | +message UpdateBookRequest { |
| 197 | + // The book to update. |
| 198 | + // |
| 199 | + // The book's `path` field is used to identify the book to be updated. |
| 200 | + // Format: publishers/{publisher}/books/{book} |
| 201 | + Book book = 1 [(google.api.field_behavior) = REQUIRED]; |
| 202 | + |
| 203 | + // The list of fields to be updated. |
| 204 | + google.protobuf.FieldMask update_mask = 2; |
| 205 | + |
| 206 | + // If set to true, and the book is not found, a new book will be created. |
| 207 | + // In this situation, `update_mask` is ignored. |
| 208 | + bool allow_missing = 3; |
| 209 | +} |
| 210 | +``` |
| 211 | + |
| 212 | +{% tab oas %} |
| 213 | + |
| 214 | +**Note:** OAS example not yet written. |
| 215 | + |
| 216 | +{% endtabs %} |
| 217 | + |
| 218 | +More specifically, the `allow_missing` flag triggers the following behavior: |
| 219 | + |
| 220 | +- If the method call is on a resource that does not exist, the resource is |
| 221 | + created. All fields are applied regardless of any provided field mask. |
| 222 | + - However, if any required fields are missing or fields have invalid values, |
| 223 | + an `INVALID_ARGUMENT` error is returned. |
| 224 | +- If the method call is on a resource that already exists, and all fields |
| 225 | + match, the existing resource is returned unchanged. |
| 226 | +- If the method call is on a resource that already exists, only fields declared |
| 227 | + in the field mask are updated. |
| 228 | + |
| 229 | +The user **must** have the update permissions to call `Update` even with |
| 230 | +`allow_missing` set to `true`. |
| 231 | + |
| 232 | +### Etags |
| 233 | + |
| 234 | +An API may sometimes need to allow users to send update requests which are |
| 235 | +guaranteed to be made against the most current data (a common use case for this |
| 236 | +is to detect and avoid race conditions). Resources which need to enable this do |
| 237 | +so by including a `string etag` field, which contains an opaque, |
| 238 | +server-computed value representing the content of the resource. |
| 239 | + |
| 240 | +In this situation, the resource **should** contain a `string etag` field: |
| 241 | + |
| 242 | +{% tab proto %} |
| 243 | + |
| 244 | +```proto |
| 245 | +message Book { |
| 246 | + option (google.api.resource) = { |
| 247 | + type: "library.example.com/Book" |
| 248 | + pattern: "publishers/{publisher}/books/{book}" |
| 249 | + }; |
| 250 | + |
| 251 | + // The resource path of the book. |
| 252 | + // Format: publishers/{publisher}/books/{book} |
| 253 | + string path = 1 [(google.api.field_behavior) = IDENTIFIER]; |
| 254 | + |
| 255 | + // The title of the book. |
| 256 | + // Example: "Mary Poppins" |
| 257 | + string title = 2; |
| 258 | + |
| 259 | + // The author of the book. |
| 260 | + // Example: "P.L. Travers" |
| 261 | + string author = 3; |
| 262 | + |
| 263 | + // The etag for this book. |
| 264 | + // If this is provided on update, it must match the server's etag. |
| 265 | + string etag = 4; |
| 266 | +} |
| 267 | +``` |
| 268 | + |
| 269 | +{% tab oas %} |
| 270 | + |
| 271 | +**Note:** OAS example not yet written. |
| 272 | + |
| 273 | +{% endtabs %} |
| 274 | + |
| 275 | +The `etag` field **may** be either [required][] or [optional][]. If it is set, |
| 276 | +then the request **must** succeed if and only if the provided etag matches the |
| 277 | +server-computed value, and **must** fail with an `ABORTED` error otherwise. The |
| 278 | +`update_mask` field in the request does not affect the behavior of the `etag` |
| 279 | +field, as it is not a field _being_ updated. |
| 280 | + |
| 281 | +### Expensive fields |
| 282 | + |
| 283 | +APIs sometimes encounter situations where some fields on a resource are |
| 284 | +expensive or impossible to reliably return. |
| 285 | + |
| 286 | +This can happen in a few situations: |
| 287 | + |
| 288 | +- A resource may have some fields that are very expensive to compute, and that |
| 289 | + are generally not useful to the customer on update requests. |
| 290 | +- A single resource sometimes represents an amalgamation of data from multiple |
| 291 | + underlying (and eventually consistent) data sources. In these situations, it |
| 292 | + may be infeasible to return authoritative information on the fields that were |
| 293 | + not changed. |
| 294 | + |
| 295 | +In this situation, an API **may** return back only the fields that were updated |
| 296 | +and omit the rest. APIs that do this **must** document this behavior. |
| 297 | + |
| 298 | +### Errors |
| 299 | + |
| 300 | +See [errors][], in particular [when to use PERMISSION_DENIED and NOT_FOUND |
| 301 | +errors][permission-denied]. |
| 302 | + |
| 303 | +In addition, if the user does have proper permission, but the requested |
| 304 | +resource does not exist, the service **must** error with `NOT_FOUND` (HTTP 404) |
| 305 | +unless `allow_missing` is set to `true`. |
| 306 | + |
| 307 | +<!-- prettier-ignore-start --> |
| 308 | +[aep-121]: ./0121.md |
| 309 | +[aep-128]: ./0128.md |
| 310 | +[aep-203]: ./0203.md |
| 311 | +[create]: ./0133.md |
| 312 | +[errors]: ./0193.md |
| 313 | +[management plane]: ./0111.md#management-plane |
| 314 | +[permission-denied]: ./0193.md#permission-denied |
| 315 | +[state fields]: ./0216.md |
| 316 | +[strong consistency]: ./0121.md#strong-consistency |
| 317 | +[required]: ./0203.md#required |
| 318 | +[optional]: ./0203.md#optional |
| 319 | +<!-- prettier-ignore-end --> |
0 commit comments