Skip to content

Commit 6a61b8c

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

File tree

3 files changed

+57
-34
lines changed

3 files changed

+57
-34
lines changed

chapters/common-headers.adoc

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -192,22 +192,29 @@ Please see <<optimistic-locking>> for a detailed discussion and options.
192192
[#230]
193193
== {MAY} Consider Using `Idempotency-Key` Header
194194

195-
When creating or updating a resource it can be helpful or necessary to prevent
196-
duplicate execution to ensure idempotency of otherwise non-idempotent requests
197-
from perspective of the client. This can be simply achieved by sending a client
198-
specific unique request key via the {Idempotency-Key} header.
199-
200-
The unique request key is not part of the resource. It is stored temporarily,
201-
e.g. for 24 hours, together with the response and a request hash (optionally).
202-
The service looks up the key to ensure idempotent behavior by sending the
203-
response without executing the request a second time. This allows clients to
204-
safely retry requests after timeouts and receive the same response.
205-
206-
*Note:* To grant the single execution semantic, the request cache and the
207-
resource have to be created or updated in the same transaction. In this case
208-
clients may require fallback strategies for retries.
209-
210-
The {Idempotency-Key} header should be defined as follows:
195+
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.
199+
200+
The _unique request key_ is stored temporarily, e.g. for 24 hours, together
201+
with the response and a request hash (optionally) in a key cache. The service
202+
can now look up the _unique request key_ in the key cache and serve the
203+
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.
207+
208+
This allows clients to safely retry requests after timeouts etc while receive
209+
the same response multiple times.
210+
211+
*Note:* To grant a reliable idempotent execution semantic, the resource and
212+
the key cache have to be updated with hard transaction semantics – considering
213+
all potential pitfalls of failures, timeouts, and concurrent requests in a
214+
distributed systems. This makes a correct implementation very hard
215+
216+
The {Idempotency-Key} header must be defined as follows, but you are free to
217+
choose your expiration time:
211218

212219
[source,yaml]
213220
----

chapters/http-requests.adoc

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ collection is empty) or 404 (if the collection is missing)
1919
* {GET} requests must NOT have a request body payload
2020

2121
*Note:* {GET} requests on collection resources should provide sufficient
22-
<<filter, #137>> and <<pagination>> mechanisms.
22+
<<137, filter>> and <<pagination>> mechanisms.
2323

2424

2525
[[get-with-body]]
@@ -54,7 +54,7 @@ settings. Thus, switching to headers does not solve the original problem.
5454
=== PUT
5555

5656
{PUT} requests are used to *update* (in rare cases to create) *entire*
57-
resources - single or collection resources. The semantic is best described
57+
resources single or collection resources. The semantic is best described
5858
as _"please put the enclosed representation at the resource mentioned by
5959
the URL, replacing any existing resource."_.
6060

@@ -66,7 +66,7 @@ implicitly creating before updating
6666
addressed by the URL with the representation passed in the payload (subsequent
6767
reads will deliver the same payload)
6868
* 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
69+
was updated with or without actual content returned), and 201 (if the
7070
resource was created)
7171

7272
*Important:* It is best practice to prefer {POST} over {PUT} for creation of
@@ -95,9 +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 been
99-
updated), 201 (if resources have been created), and 202 (if the request was
100-
accepted but has not been finished yet)
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).
101102

102103
The semantic for single resource endpoints is best described as _"please
103104
execute the given well specified request on the resource identified by the
@@ -246,24 +247,30 @@ lost updates, e.g. if same resources may be created or changed in parallel or
246247
multiple times. To design an idempotent API owners should consider to apply
247248
one of the following three patterns.
248249

249-
* A client specific *idempotency key* provided as {Idempotency-Key} header
250-
in the request. The key is not part of the resource but stored temporarily
251-
pointing to the resource to ensure idempotent behavior when _re-sending_
252-
a request from the same client - for some time (see <<230>>).
253-
* A resource specific *secondary key* provided as resource property in the
254-
request body. The _secondary key_ is stored permanently in the resource as
255-
_alternate_ (or _unique foreign key_, if it links to an another - often
256-
parent - resource, e.g. the shopping cart ID is a natural _unique foreign
257-
key_ candidate for an order. It can be used to ensure idempotent request
258-
behavior in case of multiple independent requests of clients.
259-
* A resource specific *conditional key* provided as <<182,`If-Match` header>>
250+
* A resource specific *conditional key* provided via <<182,`If-Match` header>>
260251
in the request. The key is in general a meta information of the resource,
261252
e.g. a _hash_ or _version number_, often stored with it. It allows to detect
262253
concurrent creations and updates to ensure idempotent request behavior
263254
(see <<182>>).
255+
* 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>>).
262+
* A client specific *idempotency key* provided via {Idempotency-Key} header
263+
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>>).
266+
267+
*Note:* While *conditional key* and *secondary key* are focused on handling
268+
concurrent requests, the *idempotency key* is focused on providing exact same
269+
responses and can be combined with the other patterns.
264270

265271
To decide, which pattern is suitable for your use case, please consult the
266-
following table showing the major properties of each pattern:
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:
267274

268275
[,cols="40%,20%,20%,20%",options="header",]
269276
|========================================================================================
@@ -279,6 +286,14 @@ following table showing the major properties of each pattern:
279286
(before {PUT}/{POST}/{PATCH}) | {YES} | {YES} | {NO}
280287
|========================================================================================
281288

289+
If you mainly aim to support safe retries, we suggest to apply <<182,
290+
conditional key>> and <<231,secondary key>> pattern before the <<230,
291+
Idempotency Key>> pattern.
292+
293+
[#231]
294+
== {MAY} Use Secondary Key for Idempotent `POST`
295+
296+
282297
[#154]
283298
== {SHOULD} Define Collection Format of Query Parameters and Headers
284299

index.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
:ETag: pass:[<a href="https://tools.ietf.org/html/rfc7232#section-2.3"><code>ETag</code></a>]
4040
:If-Match: pass:[<a href="https://tools.ietf.org/html/rfc7232#section-3.1"><code>If-Match</code></a>]
4141
:If-None-Match: pass:[<a href="https://tools.ietf.org/html/rfc7232#section-3.2"><code>If-None-Match</code></a>]
42+
:Location: pass:[<a href="https://tools.ietf.org/html/rfc7231#section-7.1.2"><code>Location</code></a>]
4243
:Idempotency-Key: pass:[<a href="#230"><code>Idempotency-Key</code></a>]
4344

4445

0 commit comments

Comments
 (0)