Kas yra veiksmo pakartojamumas (idempotency)? Kodėl jo reikia apskaitos ir mokėjimų API
Kaip kartojama užklausa sukuria pasikartojančią sąskaitą faktūrą, ką garantuoja pakartojamumo raktas, su kokiomis trigčių situacijomis jis turi susidoroti ir kaip jį teisingai įdiegti.
Operacija yra pakartojama (idempotentinė), jei jos atlikimas du kartus duoda tokį patį rezultatą kaip ir vienas atlikimas. Įrašo skaitymas iš prigimties yra pakartojamas. Sąskaitos faktūros išrašymas – ne: iškvieskite šią operaciją du kartus ir gausite dvi sąskaitas faktūras, du gautumnus ir klientą, kuris teiraujasi, kodėl jam buvo pateikta dviguba sąskaita.
Šis skirtumas yra labai svarbus, nes tinklai sugenda specifiniu, nemaloniu būdu: užklausa pavyksta, o atsakas prarandamas. Klientas mato skirtąjį laiką (timeout) ir negali atskirti, ar užklausa „visiškai neatkeliavo“, ar „atkeliavo, suveikė, bet atsakymas pasimetė“. Vienintelė saugiai atrodanti išeitis yra pakartoti užklausą, o būtent pakartojimas ir sukuria dublikatą.
Kodėl apskaitoje situacija dar sudėtingesnė
Daugelyje sistemų dublikatas tėra menkas nesklandumas. Didžiojoje knygoje (ledger) tai virsta didėjančia problema:
- Pasikartojanti sąskaita faktūra sunaudoja numerį iš nenutrūkstamos sekos (gapless sequence), todėl jos ištrinti negalima – nekintamumas (immutability) reiškia, kad norint ją ištaisyti, reikia išrašyti kreditinę sąskaitą (credit note), o tai yra dokumentas, kurį mato jūsų klientas.
- Dublikatas užregistruoja operacijas žurnale, todėl pajamos, PVM ir gautinos sumos bus apskaičiuotos per didelės, kol kažkas to nepastebės.
- Šis dokumentas patenka į PVM deklaraciją ir i.SAF registrus.
- Jei tai išprovokavo mokėjimą, realiai pajudėjo pinigai.
Strategija „tiesiog patikrinkime dublikatus“ irgi neveikia, nes dvi visiškai atskiros sąskaitos tam pačiam klientui, tai pačiai sumai ir tą pačią dieną yra visiškai normalus reiškinys. Sistema negali atspėti intencijos iš turinio – klientas privalo ją nurodyti pats.
Kaip veikia pakartojamumo raktai
Klientas kiekvienai loginei operacijai sugeneruoja unikalų raktą ir nusiunčia jį kartu su užklausa:
POST /v1/sales/invoices/create
Idempotency-Key: 8f14e45f-ea1b-4c2b-9f3a-2d7c1e0b5a91
Tuomet serveris garantuoja:
- Pirma užklausa su šiuo raktu – apdorojama įprastai, o rezultatas išsaugomas susiejant jį su raktu.
- Pakartojimas su tuo pačiu raktu ir tuo pačiu duomenų paketu (payload) – operacija nevyksta iš naujo, grąžinamas išsaugotas atsakas.
- Tas pats raktas, kitoks duomenų paketas – užklausa atmetama. Raktas patvirtina, kad tai ta pati operacija; kitoks užklausos turinys reiškia, kad tai kažkas kita, o tylus seno rezultato grąžinimas būtų blogiau nei klaida.
- Pakartojimas kol pirminė užklausa dar vykdoma – atmetama grąžinant klaidą, kurią galima pakartoti (retryable error), o ne vykdant operacijas lygiagrečiai.
Ketvirtas punktas yra tas, kurį elementarios diegimo sistemos dažnai praleidžia. Klientas, kuriam po 5 sekundžių suveikia laiko limito pertrauka (timeout) ir kuris po 6 sekundžių pakartoja užklausą, susidurs su savo paties tebevykstančia užklausa; be apsaugos nuo lygiagretaus vykdymo, įsigalios abi užklausos ir gausite dublikatą, kuriam išvengti šis mechanizmas ir buvo sukurtas.
Kaip teisingai generuoti raktus
Raktas turi identifikuoti operaciją, o ne bandymą:
- Teisingai: UUID, sugeneruotas vieną kartą vartotojui spustelėjus mygtuką „išrašyti sąskaitą faktūrą“, naudojamas pakartotinai kiekvienam to paties paspaudimo bandymui.
- Teisingai: deterministinis raktas, kildinamas iš jūsų pačių domeno –
order-1041-invoice. - Neteisingai: naujas UUID kiekvienam HTTP bandymui. Kiekvienas pakartojimas gauna naują raktą, todėl kiekvienas pakartojimas tampa nauja operacija ir jūs iš esmės nieko neįdiegėte.
- Neteisingai: duomenų paketo maišos (hash) reikšmė. Tokiu atveju dvi iš tikrųjų identiškos operacijos susidurs, o antroji bus tyliai nutylėta.
Raktams reikia saugojimo termino – pakankamai ilgo, kad būtų padengti realistiški pakartojimo langai ir kliento sistemos triktys, tačiau ne amžino.
Ko šis mechanizmas neišsprendžia
Pakartojamumas apsaugo nuo pasikartojančio tos pačios užklausos vykdymo. Jis neapsaugo nuo:
- Dviejų skirtingų vartotojų, sukuriančių tokią pačią sąskaitą iš skirtingų sesijų – tai verslo taisyklių problema, sprendžiama unikalumo apribojimais, pavyzdžiui, viena sąskaita vienam užsakymui.
- Loginių dublikatų su skirtingais raktais.
- Nedeterminizmo operacijos viduje – jei apdorojimo programa (handler) sugeneruoja laiko žymą arba numerį, išsaugotame atsake bus užfiksuotas pirmasis variantas; tai yra pageidaujamas elgesys, tačiau jį verta suprasti.
Šis mechanizmas papildo duomenų bazės apribojimus, o ne juos pakeičia. Nordlet didžioji knyga naudoja abu: pakartojamumo raktai sustabdo pakartotines užklausas, o unikalūs indeksai stabdo pasikartojančius atsiskaitymų paketus pagal išmokėjimo ID, pasikartojančias tiekėjų sąskaitas faktūras pagal dokumento numerį ir pasikartojančius nusidėvėjimo skaičiavimus pagal turtą bei laikotarpį.
Kaip tai įgyvendinta Nordlet sistemoje
Siųskite Idempotency-Key antraštę (header) su bet kuria būseną keičiančia užklausa – tai turi būti netuščia eilutė, sudaryta ne daugiau kaip iš 255 simbolių.
Pirmoji užklausa su konkrečiu raktu įvykdoma, o jos rezultatas išsaugomas. Pakartojimas su tuo pačiu raktu ir tuo pačiu duomenų paketu grąžina išsaugotą atsaką iš naujo nevykdydamas operacijos, todėl sąskaita faktūra sukuriama lygiai vieną kartą, nesvarbu, kiek kartų šis iškvietimas kartojamas.
Dvi trigčių situacijos yra traktuojamos aiškiai, o ne tyliai:
- Tas pats raktas, kitoks duomenų paketas → atmetama: "Idempotency-Key was already used with a different request payload." Taip pagaunamas iš tikrųjų pavojingas atvejis, kai klientas pakartotinai naudoja raktą ir pakeičia sumą.
- Tas pats raktas, pirminė užklausa vis dar vykdoma → atmetama kaip tinkama pakartoti: "A request with this Idempotency-Key is still being processed, retry shortly." Jokio lygiagretaus dvigubo vykdymo.
Raktai yra priskiriami įmonės (company) lygmeniui, todėl du nuomininkai (tenants), sugeneravę tokį patį UUID, niekada nesusidurs.
Papildoma garantija veikia priešinga kryptimi. Sąsajos pranešimų (webhook) pristatymas vyksta bent vieną kartą (at-least-once), o tai reiškia, kad jūsų galinis taškas (endpoint) kartais gaus tą patį įvykį dukart – po laiko limito pertraukos, kai pristatymas iš tikrųjų pavyko, arba pakartotinio pristatymo atveju. Kiekvienas įvykis turi ID, todėl jūsų apdorojimo programa turėtų registruoti apdorotus ID ir ignoruoti pasikartojimus. Pakartojamumas yra savybė, kurios reikia abiem integracijos pusėms; API konvencijų vadove užklausų pusė aprašyta išsamiai.
D.U.K.
Kas yra pakartojamumo raktas?
Tai kliento sugeneruotas identifikatorius, siunčiamas kartu su užklausa, leidžiantis serveriui atpažinti to paties loginio veiksmo pakartojimą. Pirmoji užklausa įvykdoma, o pakartojimai su tuo pačiu raktu grąžina išsaugotą rezultatą vietoj pakartotinio vykdymo.
Kodėl apskaitos API reikalingas pakartojamumas?
Kadangi dubliuotas įrašas sukuria realų dokumentą su realiomis pasekmėmis – antrą sąskaitą faktūrą, kuri sunaudoja eilės numerį, užsiregistruoja didžiojoje knygoje ir patenka į PVM deklaraciją. Skirtingai nuo dubliuoto skaitymo, jos negalima tiesiog ištrinti; ją reikia ištaisyti kreditine sąskaita.
Ar turėčiau generuoti naują raktą kiekvienam pakartojimui?
Ne – tai visiškai sunaikintų šio mechanizmo paskirtį. Sugeneruokite vieną raktą kiekvienai loginei operacijai ir naudokite jį pakartotinai kiekvienam tos operacijos bandymui. Naujas raktas kiekvienam bandymui reikštų, kad kiekvienas bandymas traktuojamas kaip nauja operacija.
Kas nutiks, jei pakartotinai panaudosiu raktą su kitu duomenų paketu?
Užklausa bus atmesta. Raktas patvirtina, kad tai ta pati operacija, todėl pakeistas užklausos turinys reiškia, kad kažkas negerai – dažniausiai tai rakto pakartotinio naudojimo klaida kliento pusėje. Tylus ankstesnio rezultato grąžinimas būtų blogesnė išeitis.
Ar pakartojamumas apsaugo nuo visų dublikatų?
Ne. Jis apsaugo nuo pasikartojančio tos pačios užklausos vykdymo. Du skirtingi vartotojai, nepriklausomai vienas nuo kito sukūrę tokią pačią sąskaitą faktūrą, atliko dvi skirtingas užklausas, o tai yra verslo taisyklių ir unikalumo apribojimų problema, kurios pakartojamumo raktas negali pastebėti.