Preskočiť na obsah

Idempotencia: bezpečné opakovanie požiadaviek

Sieť sa dokáže pokaziť medzi „server prácu urobil“ a „vy ste dostali odpoveď“. Kľúč idempotencie vám dovolí zopakovať POST bez toho, aby sa práca urobila dvakrát, takže vypršanie času nikdy nevytvorí duplicitný lead ani faktúru. Anglická verzia: Idempotency.

K POST požiadavke pridajte hlavičku Idempotency-Key s najviac 255 znakmi. Prvá požiadavka s daným kľúčom prebehne normálne a jej odpoveď sa uloží na 24 hodín pre daný API kľúč. Opakovanie s rovnakým kľúčom a rovnakou požiadavkou vráti uložený stav a telo bez ďalšieho spracovania a odpoveď má hlavičku Idempotent-Replayed: true. Bez hlavičky sa požiadavky nikdy nededuplikujú. Týka sa to len POST.

Dakoty si vezme odtlačok z metódy, cesty, dopytu a tela. Ten istý kľúč s iným telom alebo na inom koncovom bode vráti 422 IDEMPOTENCY_KEY_REUSED. Pri opakovaní pošlite totožné telo, vrátane poradia polí a hodnôt. Kľúče sú viazané na jeden API kľúč, takže dva kľúče môžu použiť rovnaký reťazec bez kolízie.

Ukladajú a opakujú sa úspešné odpovede (2xx) a deterministické chyby validácie (400 a 422). Všetko ostatné kľúč uvoľní: chyby 5xx, 401, 403, 404, 409 a 429 sa neukladajú, takže po oprave príčiny môžete zopakovať požiadavku s tým istým kľúčom. Odpovede väčšie ako 1 MiB sa tiež neukladajú. Ak je úložisko kľúčov nedostupné, požiadavka sa vykoná bez deduplikácie, preto si pri kritických zápisoch držte vlastnú ochranu pred duplicitami.

Ak zopakujete požiadavku, kým prvá ešte beží, dostanete 409 IDEMPOTENCY_IN_PROGRESS. Chvíľu počkajte a pošlite tú istú požiadavku znova, po dokončení prvej dostanete jej uloženú odpoveď.

Použite hodnotu, ktorá je stabilná pre jednu logickú operáciu a jedinečná medzi operáciami. Funguje UUID vytvorené raz pre jednu obchodnú akciu aj identifikátor z vášho systému, napríklad objednavka-4711-faktura. Nevytvárajte nový kľúč vnútri slučky opakovania, lebo potom by každý pokus vyzeral ako nová operácia.

Kľúč vznikne raz pred prvým pokusom a používa sa pri každom opakovaní. Ukážka stavia na pomocnej funkcii zo stránky Chyby a limity a potrebuje Node.js 18 alebo novší.

import { randomUUID } from "node:crypto";
export async function createLead(lead, key = randomUUID()) {
for (let attempt = 0; attempt < 4; attempt++) {
try {
const res = await fetch("https://api.dakoty.com/api/v1/public/leads", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DAKOTY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(lead),
});
if (res.status === 409 || res.status >= 500) throw new Error(`retry ${res.status}`);
return { replayed: res.headers.get("idempotent-replayed") === "true", body: await res.json() };
} catch (err) {
if (attempt === 3) throw err;
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 500));
}
}
}

Rovnaký princíp s balíkom requests. Kľúč vzniká pred slučkou a rovnaký slovník dáva pri každom pokuse rovnaké telo JSON.

import os
import time
import uuid
import requests
def create_lead(lead, key=None):
key = key or str(uuid.uuid4())
headers = {
"Authorization": f"Bearer {os.environ['DAKOTY_API_KEY']}",
"Idempotency-Key": key,
}
for attempt in range(4):
try:
r = requests.post("https://api.dakoty.com/api/v1/public/leads", json=lead, headers=headers, timeout=30)
if r.status_code != 409 and r.status_code < 500:
return r.headers.get("Idempotent-Replayed") == "true", r.json()
except requests.RequestException:
if attempt == 3:
raise
time.sleep(2**attempt * 0.5)
raise RuntimeError("Dakoty did not accept the request after 4 attempts")