Skip to content

Commit 4e7108b

Browse files
author
tkrop
committed
feat: improve idempotency guideline (zalando#456)
1 parent 6a61b8c commit 4e7108b

File tree

5 files changed

+272
-139
lines changed

5 files changed

+272
-139
lines changed

chapters/common-headers.adoc

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,12 @@ To expose conflicts between concurrent update operations via {PUT}, {POST},
138138
or {PATCH}, the `If-Match: <entity-tag>` header can be used to force the server
139139
to check whether the version of the updated entity is conforming to the
140140
requested `<entity-tag>`. If no matching entity is found, the operation is
141-
supposed a to respond with status code 412 - precondition failed.
141+
supposed a to respond with status code {412} - precondition failed.
142142

143143
Beside other use cases, `If-None-Match: *` can be used in a similar way to
144144
expose conflicts in resource creation. If any matching entity is found, the
145-
operation is supposed a to respond with status code 412 - precondition failed.
145+
operation is supposed a to respond with status code {412} - precondition
146+
failed.
146147

147148
The {ETag}, {If-Match}, and {If-None-Match} headers can be defined as
148149
follows in the API definition:
@@ -193,22 +194,22 @@ Please see <<optimistic-locking>> for a detailed discussion and options.
193194
== {MAY} Consider Using `Idempotency-Key` Header
194195

195196
When creating or updating resources it can be helpful or necessary to prevent
196-
duplicate execution to ensure idempotent response behavior. Generally, this can
197-
be achieved by sending a client specific _unique request key_ – that is not
198-
part of the resource – using the {Idempotency-Key} header.
197+
duplicate execution to ensure <<idempotent>> behavior. Generally, this can be
198+
achieved by sending a client specific _unique request key_ – that is not part
199+
of the resource – using the {Idempotency-Key} header.
199200

200201
The _unique request key_ is stored temporarily, e.g. for 24 hours, together
201202
with the response and a request hash (optionally) in a key cache. The service
202203
can now look up the _unique request key_ in the key cache and serve the
203204
response from the key cache, instead of re-executing the request, to ensure
204-
idempotent behavior. Optionally, it can check the request hash for consistency
205-
before serving the response. If the key is not in the key store, the request
206-
is executed as usual and the response is stored in the key cache.
205+
<<idempotent>> behavior. Optionally, it can check the request hash for
206+
consistency before serving the response. If the key is not in the key store,
207+
the request is executed as usual and the response is stored in the key cache.
207208

208209
This allows clients to safely retry requests after timeouts etc while receive
209210
the same response multiple times.
210211

211-
*Note:* To grant a reliable idempotent execution semantic, the resource and
212+
*Note:* To grant a reliable <<idempotent>> execution semantic, the resource and
212213
the key cache have to be updated with hard transaction semantics – considering
213214
all potential pitfalls of failures, timeouts, and concurrent requests in a
214215
distributed systems. This makes a correct implementation very hard

chapters/http-requests.adoc

Lines changed: 86 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ Be compliant with the standardized HTTP method semantics summarized as follows:
1212

1313
{GET} requests are used to *read* either a single or a collection resource.
1414

15-
* {GET} requests for individual resources will usually generate a 404 if the
15+
* {GET} requests for individual resources will usually generate a {404} if the
1616
resource does not exist
17-
* {GET} requests for collection resources may return either 200 (if the
18-
collection is empty) or 404 (if the collection is missing)
19-
* {GET} requests must NOT have a request body payload
17+
* {GET} requests for collection resources may return either {200} (if the
18+
collection is empty) or {404} (if the collection is missing)
19+
* {GET} requests must NOT have a request body payload (see <<GET-with-Body>>)
2020

2121
*Note:* {GET} requests on collection resources should provide sufficient
2222
<<137, filter>> and <<pagination>> mechanisms.
@@ -65,9 +65,9 @@ implicitly creating before updating
6565
* on successful {PUT} requests, the server will *replace the entire resource*
6666
addressed by the URL with the representation passed in the payload (subsequent
6767
reads will deliver the same payload)
68-
* successful {PUT} requests will usually generate 200 or 204 (if the resource
69-
was updated – with or without actual content returned), and 201 (if the
70-
resource was created)
68+
* successful {PUT} requests will usually generate {200} or {204} (if the
69+
resource was updated – with or without actual content returned), and {201} (if
70+
the resource was created)
7171

7272
*Important:* It is best practice to prefer {POST} over {PUT} for creation of
7373
(at least top-level) resources. This leaves the resource ID under control of
@@ -76,7 +76,7 @@ follows.
7676

7777
*Note:* In the rare cases where {PUT} is although used for resource creation,
7878
the resource IDs are maintained by the client and passed as a URL path segment.
79-
Putting the same resource twice is required to be _idempotent_ and to result
79+
Putting the same resource twice is required to be <<idempotent>> and to result
8080
in the same single resource instance (see <<149>>).
8181

8282
*Hint:* To prevent unnoticed concurrent updates and duplicate creations when
@@ -95,10 +95,10 @@ identified by the URL"_.
9595

9696
* on a successful {POST} request, the server will create one or multiple new
9797
resources and provide their URI/URLs in the response
98-
* successful {POST} requests will usually generate 200 (if resources have
99-
been updated), 201 (if resources have been created), 202 (if the request was
100-
accepted but has not been finished yet), and exceptionally 204 with {Location}
101-
header (if the actual resource is not returned).
98+
* successful {POST} requests will usually generate {200} (if resources have
99+
been updated), {201} (if resources have been created), {202} (if the request
100+
was accepted but has not been finished yet), and exceptionally {204} with
101+
{Location} header (if the actual resource is not returned).
102102

103103
The semantic for single resource endpoints is best described as _"please
104104
execute the given well specified request on the resource identified by the
@@ -111,7 +111,7 @@ other methods sufficiently. In such cases, make sure to document the fact that
111111
*Note:* Resource IDs with respect to {POST} requests are created and maintained
112112
by server and returned with response payload.
113113

114-
*Hint:* Posting the same resource twice is *not* required to be _idempotent_
114+
*Hint:* Posting the same resource twice is *not* required to be <<idempotent>>
115115
(check <<149>>) and may result in multiple resources. However, you <<229>> to
116116
prevent this.
117117

@@ -132,8 +132,8 @@ collection is challenging
132132
instances
133133
* on successful {PATCH} requests, the server will update parts of the resource
134134
addressed by the URL as defined by the change request in the payload
135-
* successful {PATCH} requests will usually generate 200 or 204 (if resources
136-
have been updated with or without updated content returned)
135+
* successful {PATCH} requests will usually generate {200} or {204} (if
136+
resources have been updated with or without updated content returned)
137137

138138
*Note:* since implementing {PATCH} correctly is a bit tricky, we strongly suggest
139139
to choose one and only one of the following patterns per endpoint, unless
@@ -159,7 +159,7 @@ http://tools.ietf.org/html/rfc6902[JSON Patch] can shown its full power while
159159
still showing readable patch requests (see also
160160
http://erosb.github.io/post/json-patch-vs-merge-patch[JSON patch vs. merge]).
161161

162-
*Note:* Patching the same resource twice is *not* required to be _idempotent_
162+
*Note:* Patching the same resource twice is *not* required to be <<idempotent>>
163163
(check <<149>>) and may result in a changing result. However, you <<229>> to
164164
prevent this.
165165

@@ -177,14 +177,15 @@ described as _"please delete the resource identified by the URL"_.
177177
* {DELETE} requests are usually applied to single resources, not on collection
178178
resources, as this would imply deleting the entire collection
179179
* successful {DELETE} requests will usually generate 200 (if the deleted
180-
resource is returned) or 204 (if no content is returned)
181-
* failed {DELETE} requests will usually generate 404 (if the resource cannot
182-
be found) or 410 (if the resource was already deleted before)
180+
resource is returned) or {204} (if no content is returned)
181+
* failed {DELETE} requests will usually generate {404} (if the resource cannot
182+
be found) or {410} (if the resource was already deleted before)
183183

184184
*Important:* After deleting a resource with {DELETE}, a {GET} request on the
185-
resource is expected to either return 404 (not found) or 410 (gone) depending
186-
on how the resource is represented after deletion. Under no circumstances the
187-
resource must be accessible after this operation on its endpoint.
185+
resource is expected to either return {404} (not found) or {410} (gone)
186+
depending on how the resource is represented after deletion. Under no
187+
circumstances the resource must be accessible after this operation on its
188+
endpoint.
188189

189190

190191
[[head]]
@@ -198,7 +199,7 @@ body.
198199

199200
*Hint:* {HEAD} is particular useful to efficiently lookup whether large
200201
resources or collection resources have been updated in conjunction with the
201-
https://tools.ietf.org/html/rfc7232#section-2.3[`ETag`-header].
202+
{ETag}-header.
202203

203204
[[options]]
204205
=== OPTIONS
@@ -218,10 +219,18 @@ self-describe the full functionality of a resource.
218219

219220
An operation can be...
220221

221-
* idempotent, i.e. operation will have the same effect on the server's state,
222-
if executed once or multiple times (note: this does not necessarily mean
223-
returning the same response or status code)
224-
* safe, i.e. must not have side effects such as state changes
222+
* [[idempotent, idempotent]]{RFC-idempotent} - the operation has the same
223+
_intended effect_ on the server state, independently whether it is executed
224+
once or multiple times. *Note:* this does not mean that it is returning the
225+
same response or status code.
226+
* [[safe, safe]]{RFC-safe} - the operation semantic is defined to be read-only,
227+
meaning it must not have _intended side effects_, i.e. changes, to the server
228+
state.
229+
230+
*Note:* The above definitions, of _intended (side) effect_ allows the server
231+
to provide additional state changing behavior as logging, accounting, pre-
232+
fetching, etc. However, these actual effects and state changes, must not be
233+
intended by the operation so that it can be held accountable.
225234

226235
Method implementations must fulfill the following basic properties:
227236

@@ -242,57 +251,75 @@ Method implementations must fulfill the following basic properties:
242251
== {SHOULD} Consider To Design `POST` and `PATCH` Idempotent
243252

244253
In many cases it is helpful or even necessary to design {POST} and {PATCH}
245-
idempotent for clients to expose conflicts and prevent duplicate creation or
246-
lost updates, e.g. if same resources may be created or changed in parallel or
247-
multiple times. To design an idempotent API owners should consider to apply
248-
one of the following three patterns.
254+
<<idempotent>> for clients to expose conflicts and prevent resource duplicate
255+
(a.k.a. zombie resources) or lost updates, e.g. if same resources may be
256+
created or changed in parallel or multiple times. To design an <<idempotent>>
257+
API endpoint owners should consider to apply one of the following three
258+
patterns.
249259

250260
* A resource specific *conditional key* provided via <<182,`If-Match` header>>
251261
in the request. The key is in general a meta information of the resource,
252262
e.g. a _hash_ or _version number_, often stored with it. It allows to detect
253-
concurrent creations and updates to ensure idempotent request behavior
254-
(see <<182>>).
263+
concurrent creations and updates to ensure <<idempotent>> behavior (see
264+
<<182>>).
255265
* A resource specific *secondary key* provided as resource property in the
256-
request body. The _secondary key_ is stored permanently in the resource as
257-
_alternate_ (or _unique foreign key_, if it links to an another – often
258-
parent – resource, e.g. the shopping cart ID is a natural _unique foreign
259-
key_ candidate for an order. It allows to ensure idempotent request behavior
260-
in case of multiple independent resource creations from different clients
261-
(see <<231>>).
266+
request body. The _secondary key_ is stored permanently in the resource. It
267+
allows to ensure <<idempotent>> behavior by looking up the resource in case
268+
of multiple independent resource creations from different clients (see
269+
<<231>>).
262270
* A client specific *idempotency key* provided via {Idempotency-Key} header
263271
in the request. The key is not part of the resource but stored temporarily
264-
pointing to the original response to ensure idempotent response behavior
265-
when retrying a request (see <<230>>).
272+
pointing to the original response to ensure <<idempotent>> behavior when
273+
retrying a request (see <<230>>).
266274

267275
*Note:* While *conditional key* and *secondary key* are focused on handling
268276
concurrent requests, the *idempotency key* is focused on providing exact same
269277
responses and can be combined with the other patterns.
270278

271279
To decide, which pattern is suitable for your use case, please consult the
272-
following table showing the major properties of each pattern. As far as this
273-
patterns can be applied to {PUT} we added it to allow full comparison:
274-
275-
[,cols="40%,20%,20%,20%",options="header",]
276-
|========================================================================================
277-
| | Conditional Key | Secodary Key | Idempotency Key
278-
| Applicable with | {PUT}/{PATCH} | {POST} | {POST}/{PUT}/{PATCH}
279-
| HTTP Standard | {NO} | {NO} | {NO}
280-
| Prevents concurrent creation | {YES} | {YES} | {NO}
281-
| Prevents concurrent (lost) updates | {YES} | {NO} | {NO}
282-
| Supports safe retries | {YES} | {YES} | {YES}
283-
| Supports exact same response | {NO} | {NO} | {YES}
284-
| Can be inspected (by intermediaries) | {YES} | {NO} | {YES}
285-
| Usable without additional {GET}
286-
(before {PUT}/{POST}/{PATCH}) | {YES} | {YES} | {NO}
287-
|========================================================================================
280+
following table showing the major properties of each pattern:
281+
282+
[,cols="46%,18%,18%,18%",options="header",]
283+
|==================================================================================
284+
| | Conditional Key | Secondary Key | Idempotency Key
285+
| Applicable with | {PATCH} | {POST} | {POST}/{PATCH}
286+
| HTTP Standard | {YES} | {NO} | {NO}
287+
| Prevents duplicate (zombie) resources | {YES} | {YES} | {NO}
288+
| Prevents concurrent lost updates | {YES} | {NO} | {NO}
289+
| Supports safe retries | {YES} | {YES} | {YES}
290+
| Supports exact same response | {NO} | {NO} | {YES}
291+
| Can be inspected (by intermediaries) | {YES} | {NO} | {YES}
292+
| Usable without previous {GET} | {NO} | {YES} | {YES}
293+
|==================================================================================
294+
295+
*Note:* The patterns applicable to {PATCH} can be applied in the same way to
296+
{PUT} and {DELETE} providing the same properties.
288297

289298
If you mainly aim to support safe retries, we suggest to apply <<182,
290299
conditional key>> and <<231,secondary key>> pattern before the <<230,
291300
Idempotency Key>> pattern.
292301

293302
[#231]
294-
== {MAY} Use Secondary Key for Idempotent `POST`
295-
303+
== {Should} Use Secondary Key for Idempotent `POST` Design
304+
305+
The most important pattern to design {POST} <<idempotent>> for creation is to
306+
introduce a resource specific *secondary key* provided in the request body, to
307+
eliminate the problem of duplicate (a.k.a zombie) resources.
308+
309+
The secondary key is stored permanently in the resource as _alternate key_
310+
or _combined key_ (i.e. consisting of multiple properties), that is visible
311+
when reading the resource. The best and often naturally existing candidate
312+
is a _unique foreign key_, that points to another resource having _one-on-one_
313+
relationship with the newly created resource, e.g. a parent process identifier.
314+
315+
A good example here for a secondary key is the shopping cart ID in an order
316+
resource.
317+
318+
*Note:* When using the secondary key pattern without {Idempotency-Key} all
319+
subsequent retries should fail with status code {409} - conflict. We suggest
320+
to avoid {200} here unless you make sure, that the delivered resource is the
321+
original one implementing a well defined behavior. Using {204} without content
322+
would be a similar well defined option.
296323

297324
[#154]
298325
== {SHOULD} Define Collection Format of Query Parameters and Headers

0 commit comments

Comments
 (0)