← Základy / Vysvětlující články
Co je to idempotence? Proč ji účetní a platební API potřebují
Jak opakovaný požadavek vytvoří duplicitní fakturu, co idempotenční klíč zaručuje, s jakými chybovými stavy se musí vypořádat a jak jej správně implementovat.
Operace je idempotentní, pokud má její provedení dvakrát stejný účinek jako její provedení jednou. Čtení záznamu je přirozeně idempotentní. Vytvoření faktury nikoliv – zavolejte tuto funkci dvakrát a máte dvě faktury, dvě pohledávky a zákazníka, který se ptá, proč mu byla účtována platba dvakrát.
Tento problém je závažný, protože sítě selhávají specifickým a nepříjemným způsobem: požadavek úspěšně proběhne, ale odpověď se ztratí. Klient zaznamená časový limit (timeout) a nedokáže rozeznat, zda požadavek "nikdy dorazil", nebo "dorazil, proběhl, ale odpověď se ztratila". Jeho jedinou bezpečnou možností je opakování pokusu a právě toto opakování vytváří duplikát.
Proč je situace v účetnictví ještě horší
Ve většině systémů je duplikát pouhou nepříjemností. V hlavní knize jde o kumulující se problém:
- Duplicitní faktura spotřebuje číslo z nepřerušené číselné řady (gapless sequence), takže její smazání nepřichází v úvahu – nezměnitelnost (immutability) znamená, že k její nápravě je nutné vystavit dobropis, což je dokument, který zákazník uvidí.
- Duplikát zaúčtuje účetní zápisy, takže výnosy, DPH i pohledávky jsou nadhodnocené, dokud si toho někdo nevšimne.
- Dostane se do přiznání k DPH a v Litvě také do registrů i.SAF.
- Pokud vyvolalo platbu, skutečně se pohnuly peníze.
Ani možnost "prostě zkontrolovat duplikáty" nefunguje, protože dvě zcela samostatné faktury pro stejného zákazníka na stejnou částku ve stejný den jsou naprosto normální. Systém nedokáže odvodit záměr z obsahu – klient jej musí výslovně deklarovat.
Jak fungují idempotenční klíče
Klient vygeneruje unikátní klíč pro každou logickou operaci a odešle jej spolu s požadavkem:
POST /v1/sales/invoices/create
Idempotency-Key: 8f14e45f-ea1b-4c2b-9f3a-2d7c1e0b5a91
Server následně garantuje:
- První požadavek s tímto klíčem – zpracuje se standardně a výsledek se uloží pod daným klíčem.
- Opakovaný pokus se stejným klíčem a stejným obsahem (payload) – operace se znovu neprovede, ale vrátí se uložená odpověď.
- Stejný klíč, odlišný obsah – požadavek se zamítne. Klíč je tvrzením, že se jedná o tutéž operaci; odlišné tělo požadavku znamená, že tomu tak není, a tiché vrácení starého výsledku by bylo horší než chyba.
- Opakovaný pokus v době, kdy původní požadavek ještě běží – požadavek se odmítne s chybou umožňující opakování namísto souběžného provádění.
Čtvrtý bod naivním implementacím často uniká. Klient, u kterého vyprší časový limit po 5 sekundách a o pokus se pokusí znovu po 6 sekundách, narazí na svůj vlastní probíhající požadavek; bez ochrany proti souběhu (concurrency guard) proběhnou oba a vznikne duplikát, kterému měl mechanismus zabránit.
Správné generování klíčů
Klíč musí identifikovat operaci, nikoliv pokus:
- Správně: UUID vygenerované jednou v okamžiku, kdy uživatel klikne na tlačítko "vystavit fakturu", které se použije znovu při každém opakování tohoto kliknutí.
- Správně: Deterministický klíč odvozený z vaší vlastní domény –
order-1041-invoice. - Špatně: Nové UUID pro každý HTTP pokus. Každý opakovaný pokus získá nový klíč, takže každý pokus je novou operací a v podstatě jste nic neimplementovali.
- Špatně: Hash obsahu (payloadu). Dvě legitimně identické operace by pak kolidovaly a druhá by byla potichu ignorována.
Klíče vyžadují dobu uchování (retention period) – dostatečně dlouhou na to, aby pokryla reálná okna pro opakování pokusů a výpadky na straně klienta, nikoliv však navždy.
Co idempotence neřeší
Idempotence chrání před duplicitním provedením stejného požadavku. Nechrání před těmito situacemi:
- Dva různí uživatelé vytvářející stejnou fakturu ze dvou různých relací (sessions) – to je problém podnikových pravidel (business logic), který se řeší pomocí omezujících podmínek (constraints) pro unikalitu, jako je například jedna faktura na jednu objednávku.
- Logické duplikáty s různými klíči.
- Nedeterminismus uvnitř operace – pokud obslužná rutina (handler) vygeneruje časové razítko nebo číslo, uložená odpověď zachytí to první, což je požadované chování, ale stojí za to si jej uvědomit.
Jde o doplněk databázových omezení, nikoliv o jejich náhradu. Hlavní kniha Nordlet využívá obojí: idempotenční klíče zastavují opakované požadavky, zatímco unikátní indexy zabraňují vzniku duplicitních vypořádacích dávek pro jedno ID výplaty, duplicitních přijatých faktur pro jednoho dodavatele a číslo dokumentu a duplicitních odpisových běhů pro jedno aktivum a období.
Jak idempotenci implementuje Nordlet
U každého požadavku měnícího stav zašlete hlavičku Idempotency-Key – neprázdný řetězec o maximální délce 255 znaků.
První požadavek se zadaným klíčem se provede a jeho výsledek se uloží. Opakovaný pokus se stejným klíčem a stejným obsahem vrátí uloženou odpověď bez opětovného spuštění, takže faktura se vytvoří přesně jednou bez ohledu na to, kolikrát se volání opakuje.
Dva chybové stavy jsou řešeny explicitně namísto tichého přehlížení:
- Stejný klíč, jiný obsah → zamítnuto: "Idempotency-Key was already used with a different request payload." Tím se zachytí skutečně nebezpečný případ, kdy klient znovu použije klíč, ale změní částku.
- Stejný klíč, původní požadavek stále probíhá → zamítnuto jako opakovatelná chyba: "A request with this Idempotency-Key is still being processed, retry shortly." Nedochází k souběžnému dvojímu provádění.
Klíče jsou oddělené pro každou společnost (company), takže dva nájemci generující stejné UUID nikdy nekolidují.
Doplňková záruka funguje opačným směrem. Doručení webhooků probíhá v režimu alespoň jednou (at-least-once), což znamená, že váš koncový bod občas obdrží stejnou událost dvakrát – po vypršení časového limitu, kdy k doručení ve skutečnosti došlo, nebo při opakovaném doručení. Každá událost nese ID a váš obslužný program by měl zaznamenávat zpracovaná ID a opakování ignorovat. Idempotence je vlastnost, kterou vyžadují obě strany integrace; příručka konvencí API podrobně popisuje stranu požadavků.
FAQ
Co je to idempotenční klíč?
Identifikátor generovaný klientem, který se odesílá s požadavkem a umožňuje serveru rozpoznat opakovaný pokus o stejnou logickou operaci. První požadavek se provede; opakované pokusy se stejným klíčem vrátí uložený výsledek namísto opětovného spuštění.
Proč účetní API potřebují idempotenci?
Protože duplicitní zápis vytvoří reálný dokument se skutečnými důsledky – druhou fakturu, která spotřebuje sekvenční číslo, zaúčtuje se do hlavní knihy a vstoupí do přiznání k DPH. Na rozdíl od duplicitního čtení ji nelze jednoduše smazat; je nutné ji opravit dobropisem.
Mám pro každý opakovaný pokus generovat nový klíč?
Ne – to by celý mechanismus zcela negovalo. Vygenerujte jeden klíč pro jednu logickou operaci a použijte jej znovu při každém opakování této operace. Nový klíč pro každý pokus znamená, že s každým pokusem se zachází jako s novou operací.
Co se stane, když použiji klíč znovu s jiným obsahem?
Požadavek je zamítnut. Klíč stvrzuje, že se jedná o tutéž operaci, takže změněné tělo požadavku znamená, že je něco v neordenku – nejpravděpodobněji se jedná o chybu opakovaného použití klíče na straně klienta. Tiché vrácení dřívějšího výsledku by bylo horší.
Zabraňuje idempotence všem duplikátům?
Ne. Zabraňuje duplicitnímu provedení stejného požadavku. Dva různí uživatelé nezávisle na sobě vytvoří stejnou fakturu tím, že odeslali dva různé požadavky, což je problém podnikových pravidel pro omezení uniky, nikoliv něco, co by idempotenční klíč mohl odhalit.