Undeva, în fiecare contract de integrare, există o propoziție de genul „fiecare comandă va fi transmisă exact o dată”. Nu a fost niciodată adevărată. Rețeaua s-a partiționat în mijlocul cererii și expeditorul a reîncercat; coada a retrimis mesajul după o cădere a consumatorului; un operator de depozit a apăsat butonul de export de două ori. Duplicatele nu sunt un mod de eșec - sunt vreme.
Chei de idempotență, alese cu grijă
Soluția este veche și lipsită de strălucire: fiecare mesaj poartă o cheie, iar procesarea nu face nimic atunci când cheia a mai fost văzută. Toate deciziile interesante țin de alegerea cheii.
- Cheile naturale bat cheile generate. O sincronizare de comenzi cu cheia
order_id + statussupraviețuiește regenerării outbox-ului de către expeditor; un UUID aleatoriu creat la momentul trimiterii nu supraviețuiește. - Delimitați cheia la efect, nu la mesaj. Dacă un mesaj creează o factură și rezervă stoc, acestea sunt două efecte cu două chei - altfel, un eșec parțial vă lasă în imposibilitatea de a reîncerca vreunul dintre ele în siguranță.
- Expirați cheile în mod deliberat. Un depozit de deduplicare care crește la nesfârșit este o bombă cu ceas; unul care expiră prea repede reintroduce duplicate. Implicit, folosim 3× orizontul maxim de reîncercare al expeditorului.
Testul care contează
Fiecare integrare pe care o livrăm trece ceea ce numim intern testul dublei atingeri: se redă întregul jurnal de mesaje de ieri prin sistemul de azi, de două ori, iar starea rezultată este comparată. Dacă ceva s-a schimbat la a doua trecere, pipeline-ul nu este gata.
Este un test necruțător și eșuează constant în timpul dezvoltării - pe câmpuri de timestamp, pe câmpurile de audit „creat de”, pe contoare de secvență. Fiecare eșec este un loc în care o retrimitere la ora 3 dimineața ar fi corupt datele pe tăcute. E mult mai ieftin să prinzi problema în timpul dezvoltării decât în producție.