Nordlet

← Dokumentation / Ordliste

Hvad er idempotens? Derfor har regnskabs- og betalings-API'er brug for det

Hvordan et gentaget anmodning skaber en duplikatfaktura, hvad en idempotensnøgle garanterer, hvilke fejltilstande den skal håndtere, og hvordan den implementeres korrekt.

En handling er idempotent, hvis den har samme virkning, uanset om den udføres én eller flere gange. At læse en post er naturligt idempotent. Det er at oprette en faktura ikke - hvis du kalder det to gange, har du to fakturaer, to tilgodehavender og en kunde, der spørger, hvorfor de er blevet faktureret to gange.

Den forskel er vigtig, fordi netværk fejler på en bestemt, ubehagelig måde: Anmodningen lykkes, og svaret går tabt. Klienten ser en timeout og kan ikke skelne mellem "kom aldrig frem" og "kom frem, virkede, svar tabt". Dens eneste sikre valg er at prøve igen, og det er dette forsøg, der skaber duplikaten.

Hvorfor regnskab gør det værre

I de fleste systemer er en duplikat et irritationsmoment. I en hovedbog er det et voksende problem:

  • En duplikatfaktura forbruger et nummer fra en hulsomløbsfri sekvens (gapless sequence), så sletning er ikke en mulighed - uforanderlighed (immutability) betyder, at rettelse kræver en kreditnota, hvilket er et dokument, din kunde ser.
  • Duplikaten posterer posteringer, så omsætning, moms og tilgodehavender er overvurderede, indtil nogen opdager det.
  • Den indgår i momsangivelsen og i Litauen i i.SAF-registrene.
  • Hvis den udløste en betaling, flyttede der faktisk penge.

"Bare tjek for duplikater" virker heller ikke, fordi to ægte separate fakturaer til den samme kunde for det samme beløb på den samme dag er helt normale. Systemet kan ikke udlede hensigt ud fra indhold - klienten er nødt til at angive det.

Sådan fungerer idempotensnøgler

Klienten genererer en unik nøgle pr. logisk handling og sender den med anmodningen:

POST /v1/sales/invoices/create
Idempotency-Key: 8f14e45f-ea1b-4c2b-9f3a-2d7c1e0b5a91

Serveren garanterer derefter følgende:

  1. Første anmodning med denne nøgle - behandles normalt, og resultatet gemmes knyttet til nøglen.
  2. Forsøg igen med samme nøgle og samme payload - udfør ikke igen; returner det gemte svar.
  3. Samme nøgle, forskellig payload - afvis. Nøglen er en påstand om, at dette er den samme handling; et andet brødtekst (body) betyder, at det ikke er tilfældet, og hvis det gamle resultat blev returneret i det stille, ville det være værre end en fejl.
  4. Prøv igen, mens den oprindelige stadig kører - afvis med en fejl, der kan prøves igen, i stedet for at udføre dem sideløbende.

Punkt 4 er det punkt, som naive implementeringer overser. En klient, der timere ud efter 5 sekunder og prøver igen efter 6, vil kollidere med sin egen igangværende anmodning; uden en samtidig spærring (concurrency guard) fortsætter begge, og du får den duplikat, som mekanismen eksisterede for at forhindre.

Sådan genereres nøgler korrekt

Nøglen skal identificere handlingen, ikke forsøget:

  • Rigtigt: En UUID genereret én gang, når brugeren klikker på "udsted faktura", genbrugt ved hvert nyt forsøg på det klik.
  • Rigtigt: En deterministik nøgle udledt af dit eget domæne - order-1041-invoice.
  • Forkert: En frisk UUID pr. HTTP-forsøg. Hvert nyt forsøg får en ny nøgle, så hvert forsøg er en ny handling, og du har intet implementeret.
  • Forkert: Et hash af payloaden. To lovligt identiske handlinger vil da kollidere, og den anden sluges i stilhed.

Nøgler har brug for en opbevaringsperiode - lang nok til at dække realistiske tidsrum for gentagne forsøg og klientnedbrud, men ikke for evigt.

Hvad det ikke løser

Idempotens beskytter mod duplikeret udførelse af den samme anmodning. Den beskytter ikke mod:

  • To forskellige brugere, der opretter den samme faktura fra forskellige sessioner - det er et forretningsregelproblem, der løses af entydighedsbegrænsninger (uniqueness constraints) såsom én faktura pr. ordre.
  • Logiske duplikater med forskellige nøgler.
  • Ikke-determinisme inde i handlingen - hvis håndtereren genererer et tidsstempel eller et nummer, fanger det gemte svar det første, hvilket er den ønskede adfærd, men værd at forstå.

Det fungerer sammen med og erstatter ikke databasens begrænsninger. Nordlets hovedbog bruger begge dele: Idempotensnøgler stopper gentagne anmodninger, mens unikke indekser stopper duplikerede afregningsbatcher pr. udbetalings-id, duplikerede købsfakturaer pr. leverandør og dokumentnummer samt duplikerede afskrivningskørsler pr. aktiv og periode.

Sådan implementerer Nordlet det

Send et Idempotency-Key-header på enhver handling, der ændrer tilstand - en ikke-tom streng på højst 255 tegn.

Den første anmodning med en given nøgle udføres, og dens resultat gemmes. Et nyt forsøg med den samme nøgle og den samme payload returnerer det gemte svar uden at blive udført igen, så fakturaen oprettes præcist én gang, uanset hvor mange gange kaldet gentages.

To fejltilstande er eksplicitte i stedet for tavse:

  • Samme nøgle, forskellig payload → afvises: "Idempotency-Key was already used with a different request payload." Dette fanger det reelt farlige tilfælde, hvor en klient genbruger en nøgle, mens beløbet ændres.
  • Samme nøgle, originalen er stadig i gang → afvises som værende klar til nyt forsøg: "A request with this Idempotency-Key is still being processed, retry shortly." Ingen sideløbende dobbeltudførelse.

Nøgler er afgrænset pr. virksomhed, så to lejere, der genererer den samme UUID, kolliderer aldrig.

Den supplerende garanti kører i den anden retning. Webhook-levering er mindst én gang (at-least-once), hvilket betyder, at dit endpoint lejlighedsvis vil modtage den samme begivenhed to gange - efter en timeout, hvor leveringen faktisk lykkedes, eller ved genlevering. Hver begivenhed bærer et ID, og din håndterer bør registrere behandlede ID'er og ignorere gentagelser. Idempotens er en egenskab, som begge sider af en integration har brug for; vejledningen om API-konventioner dækker anmodningssiden i detaljer.

FAQ

Hvad er en idempotensnøgle?

En klientgenereret identifikator, der sendes med en anmodning, som giver serveren mulighed for at genkende et nyt forsøg på den samme logiske handling. Den første anmodning udføres; gentagne forsøg med den samme nøgle returnerer det gemte resultat i stedet for at udføre igen.

Hvorfor har regnskabs-API'er brug for idempotens?

Fordi en duplikeret skrivning skaber et reelt dokument med reelle konsekvenser - en anden faktura, der forbruger et sekventielt nummer, posterer til hovedbogen og indgår i en momsangivelse. I modsætning til en duplikeret læsning kan den ikke bare slettes; den skal rettes med en kreditnota.

Skal jeg generere en ny nøgle til hvert nyt forsøg?

Nej - det modarbejder mekanismen fuldstændigt. Generer én nøgle pr. logisk handling, og genbrug den ved hvert forsøg på den handling. En ny nøgle pr. forsøg betyder, at hvert forsøg behandles som en ny handling.

Hvad sker der, hvis jeg genbruger en nøgle med en anden payload?

Anmodningen afvises. Nøglen hævder, at dette er den samme handling, så en ændret brødtekst betyder, at noget er galt - sandsynligvis en fejl med nøglegenbrug hos klienten. At returnere det tidligere resultat i stilhed ville være værre.

Forhindrer idempotens alle duplikater?

Nej. Den forhindrer duplikeret udførelse af den samme anmodning. To forskellige brugere, der uafhængigt af hinanden opretter den samme faktura, har fremsat to forskellige anmodninger, hvilket er et forretningsregelproblem for entydighedsbegrænsninger og ikke noget, en idempotensnøgle kan se.