← Grunnleggende / Forklaringer
Hva er idempotens? Derfor trenger regnskaps- og betalings-API-er det
Hvordan et forespørselsforsøk som kjøres på nytt skaper en duplikatfaktura, hva en idempotensnøkkel garanterer, feilmodusene den må håndtere, og hvordan du implementerer den riktig.
En operasjon er idempotent dersom utførelsen av den to ganger gir nøyaktig samme resultat som å utføre den én gang. Å lese en post er naturlig idempotent. Å opprette en faktura er det ikke - kaller du den to ganger, sitter du igjen med to fakturaer, to kundefordringer og en kunde som lurer på hvorfor de har blitt fakturert dobbelt.
Dette gapet er kritisk fordi nettverk feiler på en spesifikk og plagsom måte: forespørselen lykkes, men responsen går tapt. Klienten opplever en tidsavbrudd (timeout) og kan ikke skille mellom "kom aldri frem" og "kom frem, ble utført, svar gikk tapt". Den eneste sikre utveien virker å være å prøve igjen, og det er nettopp denne nykjøringen som skaper duplikatet.
Hvorfor regnskap gjør det verre
I de fleste systemer er en duplikat bare tilstrekkelig irriterende. I en hovedbok (ledger) er det et forsterkende problem:
- En duplikatfaktura forbruker et nummer fra en hullerfri sekvens (gapless sequence), så det å slette den er ikke et alternativ - uforanderlighet betyr at korrigering krever en kreditnota, som er et dokument kunden din får se.
- Duplikatet bokfører bilag, slik at omsetning, MVA og fordringer blir overdrevet inntil noen oppdager det.
- Den havner i MVA-meldingen og, i Litauen, i i.SAF-registrene.
- Dersom den utløste en betaling, har penger faktisk flyttet på seg.
"Bare sjekk etter duplikat" fungerer heller ikke, fordi to oppriktig separate fakturaer til samme kunde for samme beløp på samme dag er helt normalt. Systemet kan ikke utlede hensikt ut fra innhold - klienten må eksplisitt oppgi det.
Slik fungerer idempotensnøkler
Klienten genererer en unik nøkkel per logiske operasjon og sender den med forespørselen:
POST /v1/sales/invoices/create
Idempotency-Key: 8f14e45f-ea1b-4c2b-9f3a-2d7c1e0b5a91
Serveren garanterer deretter følgende:
- Første forespørsel med denne nøkkelen - behandles normalt, og resultatet lagres knyttet til nøkkelen.
- Nytt forsøk med samme nøkkel og samme nyttelast (payload) - skal ikke utføres på nytt, men returnere den lagrede responsen.
- Samme nøkkel, annen nyttelast - avvises. Nøkkelen er en påstand om at dette er den samme operasjonen; en annen kropp (body) betyr at det ikke er det, og det å i stillhet returnere det gamle resultatet ville vært verre enn en feil.
- Nytt forsøk mens originalen fremdeles kjører - avvises med en feil som kan prøves på nytt, i stedet for å kjøre parallelt.
Punkt 4 er det naive implementeringer ofte glipper på. En klient som opplever tidsavbrudd etter 5 sekunder og prøver igjen etter 6, vil kollidere med sin egen pågående forespørsel; uten en samtidighetsbeskyttelse (concurrency guard) vil begge gå igjennom, og du sitter igjen med duplikatet mekanismen var ment å forhindre.
Generering av nøkler på riktig måte
Nøkkelen må identifisere operasjonen, ikke forsøket:
- Riktig: en UUID generert én gang når brukeren klikker på "utsted faktura", gjenbrukt for hvert nye forsøk på det samme klikket.
- Riktig: en deterministisk nøkkel avledet fra ditt eget domene -
order-1041-invoice. - Feil: en helt ny UUID per HTTP-forsøk. Hvert forsøk får en ny nøkkel, så hvert forsøk blir en ny operasjon, og du har ikke implementert noen ting.
- Feil: en hash av nyttelasten. To legitimt identiske operasjoner vil da kollidere, og den andre svelges i stillhet.
Nøkler trenger en lagringsperiode (retention period) - lang nok til å dekke realistiske vinduer for nye forsøk og klientnedetid, men ikke for alltid.
Hva det ikke løser
Idempotens beskytter mot duplisert utførelse av den samme forespørselen. Den beskytter ikke mot:
- To forskjellige brukere som oppretter den samme fakturaen fra ulike sesjoner - det er et forretningsregelproblem som løses med unikhetsbegrensninger (uniqueness constraints), for eksempel én faktura per ordre.
- Logiske duplikater med forskjellige nøkler.
- Ikke-determinisme inne i operasjonen - hvis behandleren (handler) genererer et tidsstempel eller et nummer, fanger den lagrede responsen opp den første, noe som er ønsket oppførsel, men verdt å forstå.
Den fungerer som et supplement til databasebegrensninger fremfor å erstatte dem. Nordlets hovedbok bruker begge deler: idempotensnøkler stopper forespørsler som kjøres på nytt, mens unike indekser stopper dupliserte avregningsbatcher per utbetalings-ID, dupliserte kjøpsfakturaer per leverandør og dokumentnummer, samt dupliserte avskrivningskjøringer per anleggsmiddel og periode.
Slik implementerer Nordlet det
Send en Idempotency-Key-header på alle tilstandsendrende forespørsler - en ikke-tom streng på maks 255 tegn.
Den første forespørselen med en gitt nøkkel utføres, og resultatet lagres. Et nytt forsøk med samme nøkkel og samme nyttelast returnerer den lagrede responsen uten å utføre handlingen på nytt, slik at fakturaen opprettes nøyaktig én gang uansett hvor mange ganger kallet gjentas.
To feilmoduser er eksplisitte fremfor tause:
- Samme nøkkel, annen nyttelast → avvises: "Idempotency-Key was already used with a different request payload." Dette fanger opp det genuint farlige tilfellet der en klient gjenbruker en nøkkel samtidig som beløpet endres.
- Samme nøkkel, originalen kjører fremdeles → avvises som noe som kan prøves på nytt: "A request with this Idempotency-Key is still being processed, retry shortly." Ingen samtidig doble utførelser.
Nøkler er avgrenset per selskap (scoped per company), slik at to leietakere (tenants) som genererer samme UUID, aldri kolliderer.
Den utfyllende garantien går i motsatt retning. Webhook-levering er minst én gang (at-least-once), noe som betyr at endepunktet ditt av og til vil motta den samme hendelsen to ganger - etter et tidsavbrudd der leveringen faktisk var vellykket, eller ved ny levering. Hver hendelse bærer en ID, og behandleren din bør registrere behandlede ID-er og ignorere gjentakelser. Idempotens er en egenskap begge sider av en integrasjon trenger; veiledningen for API-konvensjoner dekker forespørselssiden i detalj.
FAQ
Hva er en idempotensnøkkel?
En klientgenerert identifikator som sendes med en forespørsel, som lar serveren gjenkjenne et nytt forsøk på den samme logiske operasjonen. Den første forespørselen utføres; nye forsøk med samme nøkkel returnerer det lagrede resultatet i stedet for å utføre handlingen på nytt.
Hvorfor trenger regnskaps-API-er idempotens?
Fordi en duplisert skrivning oppretter et ekte dokument med ekte konsekvenser - en andre faktura som forbruker et sekvensielt nummer, bokføres i hovedboken og går inn i en MVA-melding. I motsetning til en duplisert lesing, kan den ikke bare slettes; den må korrigeres med en kreditnota.
Bør jeg generere en ny nøkkel for hvert forsøk?
Nei - det undergraver hele mekanismen. Generer én nøkkel per logiske operasjon og gjenbruk den for hvert forsøk på den operasjonen. En ny nøkkel per forsøk betyr at hvert forsøk behandles som en ny operasjon.
Hva skjer hvis jeg gjenbruker en nøkkel med en annen nyttelast?
Forespørselen avvises. Nøkkelen slår fast at dette er den samme operasjonen, så en endret kropp betyr at noe er feil - mest sannsynlig en feil med nøkkelgjenbruk på klienten. Å returnere det tidligere resultatet i stillhet ville ha vært verre.
Forhindrer idempotens alle duplikater?
Nei. Den forhindrer duplisert utførelse av den samme forespørselen. To forskjellige brukere som uavhengig av hverandre oppretter den samme fakturaen, har utført to forskjellige forespørsler. Dette er et forretningsregelproblem for unikhetsbegrensninger, ikke noe en idempotensnøkkel kan oppdage.