Nordlet

← Dokumentacija / Slovar

Kaj je idempotenca? Zakaj jo računovodski in plačilni API-ji potrebujejo

Kako ponovljen zahtevek ustvari podvojeni račun, kaj jamči ključ idempotence, s katerimi vrstami napak se mora spopasti in kako jo pravilno implementirati.

Operacija je idempotente, če ima njeno dvakratno izvajanje enak učinek kot enkratno. Branje zapisov je po naravi idempotento. Ustvarjanje računa pa ni - če metodo kličete dvakrat, dobite dva računa, dve terjatvi in stranko, ki sprašuje, zakaj je prejela dvojni obračun.

Ta vrzel je pomembna, ker omrežja odpovedujejo na specifičen, zoprn način: zahtevek je uspešen, vendar je odgovor izgubljen. Odjemalec zazna časovno omejitev (timeout) in ne more ločiti med stanji »ni nikoli prispelo« ter »prispelo, izvedeno, odgovor izgubljen«. Njegova edina varna izbira je ponovitev zahtevka, prav ponovitev pa povzroči podvojitev.

Zakaj je pri računovodstvu zadeva še slabša

V večini sistemov je podvojitev le manjša nevšečnost. V glavni knjigi (ledger) pa gre za kumulativno težavo:

  • Podvojen račun porabi številko iz neprekinjene zaporedne vrste (gapless sequence), zato brisanje ne pride v poštev - nespremenljivost (immutability) pomeni, da je za popravek potreben dobropis, to pa je dokument, ki ga stranka vidi.
  • Podvojeni dokument ustvari knjižbe, zato so prihodki, DDV in terjatve precenjeni, dokler nekdo tega ne opazi.
  • Podvojen račun vstopi v obračun DDV in v Litvi v evidence i.SAF.
  • Če je bil sprožen prenos plačila, se je denar dejansko premaknil.

Tudi preprosto »preverjanje podlag za podvojitve« ne deluje, saj sta dva povsem ločena računa za isto stranko, z istim zneskom in na isti dan popolnoma običajna. Sistem ne more uganiti namena iz vsebine - odjemalec ga mora izrecno navesti.

Kako delujejo ključi idempotence

Odjemalec za vsako logično operacijo ustvari enoličen ključ in ga pošlje skupaj z zahtevkom:

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

Strežnik nato zagotovi naslednje:

  1. Prvi zahtevek s tem ključem - obdelava poteka normalno, rezultat pa se shrani pod tem ključem.
  2. Ponovitev z istim ključem in istim bremenom (payload) - izvajanje se ne ponovi, temveč se vrne shranjeni odgovor.
  3. Isti ključ, drugačno breme - zahtevek je zavrnjen. Ključ predstavlja trditev, da gre za isto operacijo; drugačna vsebina pomeni, da temu ni tako, tiho vračanje starega rezultata pa bi bilo slabše od napake.
  4. Ponovitev, medtem ko se izvirni zahtevek še izvaja - zavrnitev z napako, ki omogoča ponovitev, namesto vzporednega izvajanja.

Četrta točka je tista, ki jo naivne implementacije spregledajo. Odjemalec, ki po 5 sekundah zazna časovno omejitev in po 6 sekundah ponovi zahtevek, bo trčil ob lasten zahtevek, ki je še v teku. Brez varovala pred vzporednim izvajanjem bosta uspela oba in dobili boste podvojitev, ki naj bi jo mehanizem preprečil.

Pravilno ustvarjanje ključev

Ključ mora identificirati operacijo, ne pa poskusa izvedbe:

  • Pravilno: identifikator UUID, ustvarjen enkrat, ko uporabnik klikne »izdaj račun«, ki se ponovno uporabi za vsak poskus ponovitve tega klika.
  • Pravilno: deterministični ključ, izpeljan iz vašega lastnega domenskega okolja - order-1041-invoice.
  • Napačno: nov UUID za vsak poskus HTTP. Vsak poskus dobi nov ključ, zato je vsak poskus nova operacija in niste implementirali ničesar.
  • Napačno: zgoščena vrednost (hash) bremena. V tem primeru bosta dve povsem legitimno enaki operaciji trčili, druga pa bo tiho prezrta.

Ključi potrebujejo obdobje hrambe - dovolj dolgo, da pokrijejo realne okvire za ponovitve in izpade pri odjemalcu, vendar ne za vedno.

Česar idempotenca ne rešuje

Idempotenca ščiti pred podvojeno izvedbo istega zahtevka. Ne ščiti pa pred:

  • Dvema različnima uporabnikoma, ki ustvarjata enak račun iz različnih sej - to je problem poslovnih pravil, ki se rešuje z omejitvami enoličnosti, na primer z enim računom na naročilo.
  • Logičnimi podvojitvami z različnimi ključi.
  • Nedeterminizmom znotraj operacije - če obdelovalec ustvari časovni žig ali zaporedno številko, shranjeni odgovor zajame prvega, kar je sicer želeno obnašanje, vendar ga je vredno razumeti.

Idempotenca dopolnjuje podatkovne baze in ne nadomešča njihovih omejitev. Nordletov glavni knjigi uporabljata oboje: ključi idempotence preprečujejo ponovljene zahtevke, medtem ko enolični indeksi preprečujejo podvojene serije poravnav glede na ID izplačila, podvojene prejete račune glede na dobavitelja in številko dokumenta ter podvojene izračune amortizacije glede na sredstvo in obdobje.

Kako idempotenco implementira Nordlet

Ob vsakem zahtevku, ki spreminja stanje, pošljite glavo Idempotency-Key - ta mora vsebovati neprazen niz z največ 255 znaki.

Prvi zahtevek z danim ključem se izvede, njegov rezultat pa se shrani. Ponovitev z istim ključem in istim bremenom vrne shranjeni odgovor brez ponovnega izvajanja, zato je račun ustvarjen natanko enkrat, ne glede na to, kolikokrat ponovite klic.

Dve vrsti napak sta obravnavani izrecno in ne tiho:

  • Isti ključ, drugačno breme → zavrnjeno: "Idempotency-Key was already used with a different request payload." To prepreči resnično nevaren primer, ko odjemalec znova uporabi ključ, hkrati pa spremeni znesek.
  • Isti ključ, izvirnik je še v teku → zavrnjeno kot ponovljivo: "A request with this Idempotency-Key is still being processed, retry shortly." Tako ni sočasnega dvojnega izvajanja.

Ključi so omejeni na posamezno podjetje, zato dva najemnika (tenant), ki ustvarita enak UUID, nikoli ne trčita med seboj.

Doplnilno jamstvo deluje v nasprotni smeri. Dostava spletnih kljuk (webhooks) temelji na načelu vsaj enkrat (at-least-once), kar pomeni, da bo vaša končna točka občasno prejela isti dogodek dvakrat - po časovni omejitvi, ko je bila dostava dejansko uspešna, ali ob ponovni dostavi. Vsak dogodek vsebuje ID, vaš obdelovalec pa mora zabeležiti obdelane ID-je in prezreti ponovitve. Idempotenca je lastnost, ki jo integracija potrebuje na obeh straneh; vodnik po konvencijah API-ja podrobno obravnava stran zahtevkov.

FAQ

Kaj je ključ idempotence?

Identifikator, ki ga ustvari odjemalec in ga pošlje skupaj z zahtevkom, kar strežniku omogoča prepoznavo ponovitve iste logične operacije. Prvi zahtevek se izvede; ponovitve z istim ključem vrnejo shranjeni rezultat namesto ponovnega izvajanja.

Zakaj računovodski API-ji potrebujejo idempotenco?

Ker podvojeni zapis ustvari pravi dokument s stvarnimi posledicami - drugi račun porabi zaporedno številko, se knjiži v glavno knjigo in vstopi v obračun DDV. Za razliko od podvojenega branja takega vnosa ni mogoče preprosto izbrisati, temveč ga je treba popraviti z dobropisom.

Ali naj za vsako ponovitev ustvarim nov ključ?

Ne - s tem v celoti izničite namen mehanizma. Ustvarite en ključ na logično operacijo in ga uporabite znova ob vsaki ponovitvi te operacije. Nov ključ za vsak poskus pomeni, da je vsak poskus obravnavan kot nova operacija.

Kaj se zgodi, če ponovno uporabim ključ z drugačnim bremenom?

Zahtevek je zavrnjen. Ključ potrjuje, da gre za isto operacijo, zato spremenjeno telo pomeni napako - najverjetneje napako ponovne uporabe ključa na strani odjemalca. Tiho vračanje prejšnjega rezultata bi bilo slabše.

Ali idempotenca prepreči vse podvojitve?

Ne. Preprečuje podvojeno izvajanje istega zahtevka. Dva različna uporabnika, ki neodvisno ustvarjata enak račun, sta oddala dva različna zahtevka, kar je vprašanje poslovnih pravil in omejitev enoličnosti, ki ga ključ idempotence ne more zaznati.