Veilige retries met volledige idempotency
Bij een schrijfoperatie kan het voor een client onduidelijk zijn of deze is geslaagd, bijvoorbeeld door een netwerkfout. In dat geval zal de client de operatie opnieuw proberen. Een ongewenst effect is dat er door het opnieuw proberen meer wijzigingen worden doorgevoerd dan de bedoeling was. Om dit te voorkomen is het van belang dat alle API-operaties die data wijzigen volledig idempotent zijn.
Een operatie is idempotent als het herhalen ervan geen extra effect heeft op
de toestand van de server. GET, PUT en DELETE zijn volgens de
HTTP-specificatie idempotent. Een herhaalde DELETE /items/2 zal bijvoorbeeld
de tweede keer een 404 geven, maar de toestand van de server blijft
consistent. POST en PATCH zijn dit van nature niet: een herhaalde
POST /items zou een duplicaat item aanmaken. Voor deze operaties kan
idempotent gedrag worden afgedwongen door een Idempotency-Key header toe te
voegen.
| HTTP Verb | Idempotency mechanisme |
|---|---|
| GET | Safe request RFC 9110 |
| PUT | Idempotent RFC 9110 |
| DELETE | Idempotent RFC 9110 |
| POST | Header: Idempotency-Key |
| PATCH | Header: Idempotency-Key |
Werking
- De client genereert een unieke
Idempotency-Key(bijvoorbeeld een UUID) voor een uit te voeren operatie. - De client verstuurt de
POSTofPATCHrequest met deze key in de header. - De server controleert of deze
Idempotency-Keyal eerder is ontvangen.- Bestaande key: De server verwerkt de operatie niet opnieuw, maar stuurt direct de eerder opgeslagen response terug.
- Nieuwe key: De server verwerkt de operatie en slaat de response
(statuscode en body) op, gekoppeld aan de
Idempotency-Key.
- Als de client een retry moet uitvoeren (bijvoorbeeld door een netwerkfout),
moet exact dezelfde
Idempotency-Keyworden gebruikt.
Transactionele veiligheid
Voor kritieke operaties is het aan te bevelen voor de client om de
Idempotency-Key lokaal en duurzaam op te slaan voordat de request wordt
verstuurd. Stel dat een client crasht nadat het de request heeft verstuurd, maar
voordat het een response heeft ontvangen. Bij het herstarten weet de client niet
of de operatie op de server is geslaagd. Door de Idempotency-Key vooraf op te
slaan, kan de client na een herstart de request veilig opnieuw proberen met
dezelfde key. De server zal dan ofwel de operatie alsnog uitvoeren (als de
eerste poging nooit aankwam), of de opgeslagen response retourneren (als de
eerste poging wel slaagde).
Een correcte implementatie van idempotency op de server vereist transactionele
veiligheid. Het verwerken van de business-operatie en het opslaan van de
Idempotency-Key met de bijbehorende response moeten binnen dezelfde atomaire
transactie plaatsvinden. Zonder deze garantie kan een race condition optreden,
waarbij een operatie onterecht twee keer wordt uitgevoerd.
Levensduur van de key
De server bewaart de Idempotency-Key en de bijbehorende response voor een
bepaalde tijd om de garantie te kunnen bieden. De bewaartermijn is afhankelijk
van het verwachte retry-patroon van clients, waarbij 24 uur een gangbare keuze
is. De gekozen termijn moet door de API-provider worden gedocumenteerd. Na het
verlopen van de termijn behandelt de server een request met dezelfde key als een
nieuwe operatie.
Voorbeeld in OpenAPI
paths:
/items:
post:
summary: Maak een nieuw item aan
parameters:
- name: Idempotency-Key
in: header
required: true
description:
Een unieke sleutel om de idempotent-garantie te bieden. De server
bewaart deze sleutel 24 uur.
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Item"
responses:
"201":
description: Item aangemaakt