Collegare due sistemi senza perdere dati per strada
Tentativi ripetuti, idempotenza e tracciabilità: le tre cose che distinguono un'integrazione che funziona da una che sembra funzionare.
Quasi tutte le integrazioni rotte che mi è capitato di sistemare sono nate allo stesso modo: qualcuno ha scritto uno script che legge da una parte e scrive dall’altra. Funzionava. L’hanno lasciato acceso. Sei mesi dopo mancano delle fatture e nessuno sa da quando.
Lo script non era scritto male. Gli mancavano tre cose che nessuno chiede nella riunione iniziale, perché non si vedono finché non falliscono.
1. La rete cade, e cade nel momento peggiore
Quando il tuo codice chiama una API ci sono tre finali possibili: ha funzionato, è fallito, oppure non lo sai. Il terzo è quello interessante.
Se la connessione cade dopo che la richiesta è partita ma prima che arrivi la risposta, il tuo lato registra un errore e l’altro lato registra un inserimento. Riprovi, e adesso ci sono due fatture.
La risposta non è “riprovare di meno”. È riprovare con la testa:
- Backoff esponenziale con jitter. Se riprovi ogni secondo, quando l’altro sistema cade tutti i tuoi processi lo martellano insieme e gli impediscono di rialzarsi. Aspetta 1 s, 2 s, 4 s, 8 s, e aggiungi fino a un secondo di casualità perché non si sincronizzino.
- Un tetto. Dopo N tentativi il messaggio va in una coda a parte e qualcuno lo guarda. Riprovare all’infinito è nascondere il problema.
- Sappi cosa si riprova. Un 503 si riprova. Un 400 no: la richiesta è sbagliata e lo sarà sempre. Riprovare un 400 è spendere risorse per fallire più tardi.
2. Idempotenza: poter ripetere senza rompere
Se il punto precedente ti lascia a riprovare, serve che ripetere sia innocuo. Si chiama idempotenza ed è il pezzo che si salta più spesso.
La forma pratica: ogni operazione porta una chiave propria, stabile, derivata dal dato e non dal momento. Il numero della fattura va bene. Un UUID generato prima di inviare, anche. Date.now() no, perché cambia a ogni tentativo.
Chi riceve conserva quelle chiavi e, se ne vede una ripetuta, restituisce il risultato precedente invece di creare qualcosa di nuovo. Se chi riceve è un sistema altrui che non fa niente di tutto questo, lo fai tu: prima di inserire, chiedi se esiste già. È una chiamata in più e ti evita il duplicato.
Un’integrazione idempotente si può rilanciare per intero dopo un disastro. Una che non lo è non si tocca senza paura, e la paura è il motivo per cui nessuno la sistema.
3. Tracciabilità: poter rispondere “cos’è successo all’ordine 4412?”
Questa è quella che ti salverà il rapporto con il cliente.
Ogni messaggio che attraversa porta un identificativo di correlazione che viaggia da un capo all’altro e compare in tutti i log. Quando qualcuno chiede di un ordine, cerchi quell’identificativo e vedi la storia completa: quando è entrato, quanti tentativi ci sono stati, cosa ha risposto l’altra parte, quando è stato accettato.
Senza quello, la risposta onesta a “cos’è successo al 4412?” è “non lo so”, e quella risposta costa più di tutto lo sviluppo.
Due dettagli che contano: conserva il corpo della richiesta e della risposta, almeno per un periodo, perché “ha dato errore” non serve a nulla senza il messaggio; e non conservare dati personali che non ti servono, perché anche i log sono un posto da cui le cose escono.
Il minimo per dormire tranquilli
Se stai per collegare due sistemi e hai poco tempo, l’ordine è questo:
- Una chiave di idempotenza su ogni operazione.
- Tentativi con backoff e una coda degli errori.
- Un identificativo di correlazione in tutti i log.
- Un avviso per quando la coda degli errori smette di essere vuota.
Quattro cose, si fanno in giorni e non in mesi, e sono la differenza fra un’integrazione che ti lascia dormire e una che ti costringe a controllare fogli di calcolo il lunedì mattina.
Il resto — pannelli, metriche, rielaborazioni automatiche — viene dopo, e si costruisce solo se queste quattro ci sono.