Nordlet

← Dokumentaatio / Sanasto

Mitä on idempotenssi? Miksi kirjanpito- ja maksurajapinnat tarvitsevat sitä

Miten uusittu pyyntö luo kaksoislaskun, mitä idempotenssiavain takaa, mitkä virhetilat sen on kyettävä käsittelemään ja miten se toteutetaan oikein.

Toiminto on idempotenttinen, jos sen suorittaminen kahdesti tuottaa saman lopputuloksen kuin sen suorittaminen kerran. Tietueen lukeminen on luonnostaan idempotenttista. Laskun luominen ei ole: jos kutsun sitä kahdesti, minulla on kaksi laskua, kaksi myyntisaatavaa ja asiakas, joka kysyy, miksi häntä laskutettiin kahdesti.

Tämä kuilu on tärkeä, koska verkot vikaantuvat tietyllä, ikävällä tavalla: pyyntö onnistuu, mutta vastaus katoaa matkalle. Asiakasohjelma havaitsee aikakatkaisun eikä pysty erottamaan tilannetta, jossa pyyntö ei koskaan saapunut perille, tilanteesta, jossa se saapui, toimi, mutta vastaus katosi. Sen ainoa turvalliselta vaikuttava vaihtoehto on yrittää uudelleen, ja tämä uudelleenyritys luo kaksoiskappaleen.

Miksi kirjanpito tekee tilanteesta pahemman

Useimmissa järjestelmissä kaksoiskappale on vain harmistuksen aihe. Pääkirjanpidossa se on moninkertaistuva ongelma:

  • Kaksoislasku kuluttaa numeron aukoittomasta sarjasta, joten sen poistaminen ei ole vaihtoehto muuttumattomuuden vuoksi. Korjaaminen vaatii hyvityslaskun, joka on asiakkaan nähtävillä oleva tosiasiakirja.
  • Kaksoiskappale kirjaa vientitapahtumia, joten liikevaihto, arvonlisävero ja myyntisaatavat ovat kaikki yliarvioituja, kunnes joku huomaa asian.
  • Se päätyy alv-ilmoitukseen ja Liettuassa i.SAF-rekistereihin.
  • Jos se laukaisi maksun, rahaa todella siirtyi.

"Tarkistetaan vain kaksoiskappaleiden varalta" ei sekään toimi, sillä kaksi aidosti erillistä laskua samalle asiakassuhteelle, samalla summalla ja samana päivänä ovat täysin normaaleja. Järjestelmä ei voi päätellä tarkoitusta sisällöstä, vaan asiakkaan on ilmoitettava se.

Miten idempotenssiavaimet toimivat

Asiakasohjelma luo ainutlaatuisen avaimen loogista toimintoa kohden ja lähettää sen pyynnön mukana:

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

Palvelin puolestaan takaa seuraavat seikat:

  1. Ensimmäinen pyyntö tällä avaimella - käsitellään normaalisti, tallennetaan lopputulos avaimen yhteyteen.
  2. Uudelleenyritys samalla avaimella ja samalla tietosisällöllä - ei suoriteta uudelleen, vaan palautetaan tallennettu vastaus.
  3. Sama avain, eri tietosisältö - hylätään. Avain on vakuutus siitä, että kyseessä on sama toiminto; erilainen tietosisältö tarkoittaa, ettei se ole, ja vanhan tuloksen hiljainen palaututtaminen olisi pahempi kuin virhe.
  4. Uudelleenyritys alkuperäisen ollessa yhä käynnissä - hylätään uudelleenyrityskelpoisella virheellä sen sijaan, että toiminto suoritettaisiin rinnakkain.

Neljäs kohta on se, jonka naiivit toteutukset unohtavat. Asiakas, joka saa aikakatkaisun 5 sekunnin kohdalla ja yrittää uudelleen 6 sekunnin kohdalla, törmää omaan käynnissä olevaan pyyntöönsä. Ilman rinnakkaisuuden estoa molemmat etenevät, ja seurauksena on kaksoiskappale, jonka estämiseksi mekanismi luotiin.

Avainten luominen oikein

Avaimen on yksilöitävä toiminto, ei yrityskertaa:

  • Oikein: UUID, joka luodaan kerran käyttäjän napsauttaessa "luo lasku" -painiketta ja jota käytetään uudelleen kyseisen napsautuksen jokaisella uudelleenyrityksellä.
  • Oikein: omasta domainistasi johdettu deterministinen avain, kuten order-1041-invoice.
  • Väärin: uusi UUID jokaisella HTTP-yrityskerralla. Jokainen uudelleenyritys saa uuden avaimen, joten jokainen uudelleenyritys on uusi toiminto, etkä ole toteuttanut mitään.
  • Väärin: tietosisällön tiiviste (hash). Kaksi aidosti identtistä toimintoa törmäävät tällöin keskenään, ja jälkimmäinen nielaistaan hiljaisesti.

Avaimilla on oltava säilytysaika, joka on riittävän pitkä kattamaan realistiset uudelleenyritysikkunat ja asiakkaan käyttökatkot, mutta ei ikuisuutta.

Mitä ongelmia tämä ei ratkaise

Idempotenssi suojaa saman pyynnön moninkertaiselta suorittamiselta. Se ei suojaa seuraavilta tilanteilta:

  • Kaksi eri käyttäjää luomassa saman laskun eri istunnoista: kyseessä on liiketoimintasääntöongelma, joka ratkaistaan yksilöllisyysrajoitteilla, kuten yksi lasku tilausta kohden.
  • Loogiset kaksoiskappaleet eri avaimilla.
  • Ei-determinismi toiminnon sisällä: jos käsittelijä luo aikaleiman tai numeron, tallennettu vastaus tallentaa ensimmäisen, mikä on haluttu toimintatapa, mutta on hyvä pitää mielessä.

Se täydentää tietokantarajoitteita eikä korvaa niitä. Nordletin pääkirjanpito käyttää molempia: idempotenssiavaimet pysäyttävät uudelleen yritetyt pyynnöt, kun taas yksilölliset indeksit estävät maksuerien kaksoiskirjaukset tilitystunnusta kohden, ostolaskujen kaksoiskirjaukset toimittajaa ja tositenumeroa kohden sekä poistojen kaksoisajot käyttöomaisuushyödykettä ja tilikautta kohden.

Miten Nordlet toteuttaa sen

Lähetä Idempotency-Key-otsikko (header) kaikissa tilaa muuttavissa pyynnöissä. Sen tulee olla tyhjästä poikkeava merkkijono, joka on enintään 255 merkkiä pitkä.

Ensimmäinen tietyllä avaimella tehty pyyntö suoritetaan, ja sen lopputulos tallennetaan. Samalla avaimella ja samalla tietosisällöllä tehtävä uudelleenyritys palauttaa tallennetun vastauksen suorittamatta toimintoa uudelleen, joten lasku luodaan täsmälleen kerran riippumatta siitä, kuinka monta kertaa kutsu toistetaan.

Kaksi virhetilaa käsitellään ilmoittamalla niistä suoraan hiljaisen ohittamisen sijaan:

  • Sama avain, eri tietosisältö → hylätään: "Idempotency-Key was already used with a different request payload." Tämä havaitsee aidosti vaarallisen tilanteen, jossa asiakasohjelma käyttää avainta uudelleen samalla, kun se muuttaa summaa.
  • Sama avain, alkuperäinen pyyntö yhä käynnissä → hylätään uudelleenyrityskelpoisena: "A request with this Idempotency-Key is still being processed, retry shortly." Ei rinnakkaista kaksoissuoritusta.

Avaimet rajataan yrityskohtaisesti, joten kaksi eri vuokralaista (tenant), jotka luovat saman UUID:n, eivät koskaan törmää keskenään.

Vastaava takuu toimii myös toiseen suuntaan. Verkkokutsut (webhooks) toimitetaan vähintään kerran, mikä tarkoittaa, että päätepisteesi vastaanottaa toisinaan saman tapahtuman kahdesti: sellaisen aikakatkaisun jälkeen, jossa toimitus itse asiassa onnistui, tai uudelleentoimituksen yhteydessä. Jokaisella tapahtumalla on tunniste, ja käsittelijäsi tulisi tallentaa käsitellyt tunnisteet ja ohittaa toistot. Idempotenssi on ominaisuus, jota integraation molemmat osapuolet tarvitsevat. Rajapintojen käytännöt -opas käsittelee pyyntöpuolta yksityiskohtaisesti.

FAQ

Mikä on idempotenssiavain?

Asiakasohjelman luoma tunniste, joka lähetetään pyynnön mukana ja jonka avulla palvelin tunnistaa saman loogisen toiminnon uudelleenyrityksen. Ensimmäinen pyyntö suoritetaan; saman avaimen kanssa tehdyt uudelleenyritykset palauttavat tallennetun tuloksen sen sijaan, että toiminto suoritettaisiin uudelleen.

Miksi kirjanpitorajapinnat tarvitsevat idempotenssia?

Koska kahdennettu kirjoitus luo todellisen tosiasiakirjan todellisilla seurauksilla: toisen laskun, joka kuluttaa järjestysnumeron, kirjautuu kirjanpitoon ja päätyy arvonlisäveroilmoitukselle. Toisin kuin kahdennettua lukua, sitä ei voi yksinkertaisesti poistaa, vaan se on korjattava hyvityslaskulla.

Pitäisikö minun luoda uusi avain jokaiselle uudelleenyritykselle?

Ei, sillä se kumoaa koko mekanismin tarkoituksen. Luo yksi avain loogista toimintoa kohden ja käytä sitä uudelleen kyseisen toiminnon jokaisella uudelleenyrityksellä. Uusi avain yrityskertaa kohden tarkoittaa, että jokaista yritystä kohdellaan uutena toimintona.

Mitä tapahtuu, jos käytän avainta uudelleen eri tietosisällöllä?

Pyyntö hylätään. Avain vakuuttaa, että kyseessä on sama toiminto, joten muuttunut tietosisältö tarkoittaa, että jokin on vialla, todennäköisimmin avaimen uudelleenkäyttövirhe asiakasohjelmassa. Aiemman tuloksen palauttaminen hiljaisesti olisi huonompi vaihtoehto.

Estääkö idempotenssi kaikki kaksoiskappaleet?

Ei. Se estää saman pyynnön moninkertaisen suorittamisen. Kaksi eri käyttäjää, jotka luovat itsenäisesti saman laskun, ovat tehneet kaksi eri pyyntöä. Kyseessä on liiketoimintasääntöongelma, joka ratkaistaan yksilöllisyysrajoitteilla, eikä asia, jonka idempotenssiavain voisi havaita.