Skip to content

Commit f2aa796

Browse files
rofrankelmkistler
andauthored
Adopt Google's AIP-134: Update (#115)
* Make 0134/aep.md a jinja2 template. * Copy/paste the Google Update AIP verbatim. * De-Google * A variety of small updates: * Put proto-specific content in tabs, with placeholder OAS tabs. * Remove LRO guidannce that is redundant with the LRO AEP. * Change a small number of **should**s to **must**s. * Light copy-editing. * AIP -> AEP * Reviewing -> Approved * Remove redundant header. * Fix link text * Fix tab location * Restore LRO section for consistency. * Remove redundant guidance about including path field on resource. * Update aep/general/0134/aep.md.j2 Co-authored-by: Mike Kistler <mkistler@sbcglobal.net> * De-protofy generic guidance a bit. --------- Co-authored-by: Mike Kistler <mkistler@sbcglobal.net>
1 parent d2d7f06 commit f2aa796

3 files changed

Lines changed: 320 additions & 6 deletions

File tree

aep/general/0134/aep.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

aep/general/0134/aep.md.j2

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
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 -->

aep/general/0134/aep.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
id: 134
3-
state: reviewing
3+
state: approved
44
slug: update
55
created: 2023-01-22
66
placement:

0 commit comments

Comments
 (0)