Quando un processo composto da più passaggi non riesce, eseguirlo di nuovo dall'inizio può ripetere effetti già prodotti: creare due ordini, inviare due notifiche o importare due volte lo stesso record. Il recupero parziale dei processi PHP non riusciti consiste nello stabilire quali passaggi sono confermati, quali possono essere ripetuti senza rischi e quali richiedono una compensazione o una revisione umana.
La decisione dipende dalla semantica di ogni operazione, non solo dal punto in cui si è verificata un'eccezione. Per esempio, una risposta API persa non dimostra che il sistema esterno abbia rifiutato la richiesta. Il processo potrebbe essersi completato e la connessione essersi interrotta prima che PHP ricevesse il risultato. Progettare tenendo conto di questo caso evita di confondere un'esecuzione interrotta con un'esecuzione mai avvenuta.
Scegliere tra nuovo tentativo, ripresa e compensazione

Un nuovo tentativo completo esegue di nuovo tutti i passaggi. È appropriato quando l'intero flusso è idempotente — ripeterlo lascia invariato lo stato finale — oppure quando non si sono ancora verificati effetti esterni. Se non è possibile garantire nessuna di queste condizioni, ripeterlo senza verifiche è rischioso.
Riprendere significa continuare dal primo passaggio non confermato. Richiede di registrare lo stato dei passaggi e i relativi risultati, oltre a poter recuperare o verificare l'effetto di una chiamata il cui esito è ambiguo. Non equivale a saltare tutto ciò che sembra completato: servono evidenze persistite.
Compensare consiste nell'eseguire un'azione che contrasta un effetto precedente, come annullare una prenotazione. Non sempre ripristina esattamente lo stato originale: una notifica già inviata non può essere ritirata e un pagamento acquisito può richiedere un rimborso, con i relativi tempi e registrazioni. Per questo, una compensazione è un'operazione di business esplicita, non un rollback automatico del database.
Modellare i passaggi con stati e risultati persistiti
Rappresenta il flusso come una sequenza o una macchina a stati, con passaggi dotati di nomi stabili, input identificabili e risultati persistiti. Un modello iniziale potrebbe includere stati come pending, running, succeeded, retryable, failed e manual_review. Definisci le transizioni consentite ed evita che un processo passi a uno stato finale senza salvare le evidenze necessarie.
Un record per processo può includere un identificatore stabile, il tipo di flusso, lo stato globale, la versione della definizione del processo, le date di inizio e aggiornamento, il tentativo e il motivo dell'ultima transizione. Ogni passaggio deve salvare il proprio stato, un identificatore dell'operazione, i timestamp e un riferimento al risultato pertinente. Persisti solo le informazioni necessarie per riprendere il processo o spiegarne l'esito; non copiare indiscriminatamente risposte API complete o segreti.
In PHP, il coordinatore può separare la transizione di stato dall'esecuzione del passaggio. L'aggiornamento deve essere atomico quando più worker possono acquisire lo stesso lavoro: usa una transazione o un meccanismo di lock appropriato e registra chi ha acquisito il processo e fino a quando. Un lock con scadenza deve consentire di recuperare i lavori abbandonati senza considerare completato un passaggio rimasto a metà.
Definire punti di controllo senza presumere l'“exactly once”
Salva un punto di controllo dopo ogni risultato che il sistema può confermare in modo affidabile. Per le operazioni locali, può trattarsi di una transazione che salva insieme la modifica di business e lo stato del passaggio. Per una chiamata esterna, non esiste una transazione condivisa tra il database e il provider: il processo può interrompersi dopo che il provider ha agito e prima che PHP registri la risposta.
In questo caso, usa una chiave di idempotenza, se il provider la supporta, derivata da un identificatore stabile del processo e del passaggio. Se non è supportata, controlla lo stato remoto tramite un identificatore dell'operazione prima di ripetere la richiesta. Quando non sono disponibili né idempotenza né una verifica affidabile, considera l'esito ambiguo e sottoponi il caso a revisione. Un timeout non basta per concludere che l'operazione non sia avvenuta.
Per i task asincroni, il pattern della transactional outbox consente di salvare la modifica locale e il messaggio in attesa all'interno della stessa transazione. In seguito, un worker consegna il messaggio; anche il consumer deve tollerare i duplicati, per esempio salvando gli identificatori dei messaggi elaborati. Questi meccanismi riducono le inconsistenze, ma non trasformano automaticamente un'intera integrazione distribuita in un'operazione atomica.
Stabilire limiti per la riesecuzione e la compensazione
Definisci per ogni passaggio quali errori sono transitori, quali definitivi e quali lasciano un esito sconosciuto. Gli errori transitori possono ammettere nuovi tentativi con attesa crescente e jitter; fissa un numero massimo di tentativi e una durata complessiva. Gli errori di convalida o di autorizzazione in genere non si risolvono riprovando: conviene interrompere il flusso, correggere la causa e decidere se avviare una nuova esecuzione.
Documenta per ogni effetto esterno se può essere ripetuto, verificato, compensato o non è reversibile. Mantieni il risultato quando l'operazione è valida e ripeterla sarebbe più dannoso dello stato parziale; compensa solo se esiste un'azione di business sicura e autorizzata. Interrompi il processo e inoltra il caso a un livello superiore quando i dati non consentono di determinare che cosa è accaduto, anche la compensazione fallisce o l'azione ha conseguenze finanziarie, legali o sui clienti che richiedono un'approvazione.
Una policy di compensazione deve specificare ordine, condizioni, responsabile e risultato atteso. Registra la compensazione come un nuovo passaggio, collegato all'effetto originale, invece di cancellarne la cronologia. In questo modo, il team Operations può distinguere tra un'azione mai eseguita, un'azione eseguita e una successivamente compensata.
Fornire al team Operations controlli e contesto per intervenire
La console o la procedura operativa deve mostrare lo stato globale e quello di ogni passaggio, l'ultimo errore classificato, il numero di tentativi, i riferimenti esterni e le azioni consentite. Evita di offrire un pulsante generico “riprova tutto”. Presenta opzioni circoscritte: riprovare un passaggio idempotente, verificare lo stato remoto, eseguire una compensazione o inoltrare il caso a un livello superiore.
Proteggi queste azioni con autorizzazioni basate sui ruoli; richiedi una conferma aggiuntiva per gli effetti sensibili e registra chi è intervenuto, quando, quale opzione ha scelto e perché. Se la riesecuzione modifica i dati di input, obbliga a creare una nuova esecuzione o una revisione esplicita, anziché modificare silenziosamente gli input di un processo storico.
Per diagnosticare i problemi senza esporre informazioni sensibili, conserva gli identificatori di correlazione, i codici di errore, la versione del processo e i riferimenti necessari per consultare i sistemi di origine. Oscura token, dati personali e payload completi. Definisci anche per quanto tempo conservare i log e chi può consultarli. Una traccia utile spiega che cosa è successo senza diventare una copia superflua dei dati di business.
Testare i guasti e distribuire gradualmente il recupero
Prova le interruzioni in punti specifici: prima dell'esecuzione di un passaggio, dopo che il provider ha agito ma prima di salvare la risposta, durante una compensazione e mentre due worker tentano di acquisire lo stesso processo. Verifica che lo stato sia coerente in ogni caso, che gli effetti non vengano duplicati e che ogni intervento manuale sia registrato a fini di audit.
Includi test per risposte ambigue, chiavi di idempotenza riutilizzate, dati non validi, limiti dei tentativi e modifiche di versione del flusso. I test di integrazione con dipendenze simulate possono riprodurre guasti controllati; se il provider reale si comporta diversamente, verifica anche il contratto e i meccanismi di consultazione in un ambiente adeguato.
Per un flusso esistente, inizia classificandone i passaggi in base a reversibilità e idempotenza. Poi rendi persistente lo stato di una fase circoscritta, implementa il recupero per i guasti più rischiosi e osserva i casi in sospeso prima di ampliare l'ambito. Non cancellare né azzerare i record storici per facilitare il rilascio: conserva la tracciabilità e definisci come interpretare i processi creati con versioni precedenti.
Checklist per implementare il recupero

- Ogni passaggio ha input identificabili, uno stato persistito e un risultato verificabile?
- È noto quali chiamate sono idempotenti e che cosa fare se il loro esito è ambiguo?
- Sono definiti limiti di tentativi, scadenze e classificazione degli errori?
- Le compensazioni sono definite come azioni di business, con audit e un responsabile?
- Il team Operations può verificare e intervenire con autorizzazioni adeguate, senza accedere a dati non necessari?
- Sono stati testati guasti tra i passaggi, concorrenza, riesecuzioni e compensazioni non riuscite?
- Esiste una procedura di escalation quando non è sicuro riprendere il processo automaticamente?
Il criterio pratico è conservare evidenze sufficienti per decidere il passaggio successivo e interrompere l'automazione quando non bastano. Un recupero sicuro non cerca di nascondere il guasto: rende esplicito che cosa è stato completato, che cosa è ancora in sospeso e chi può risolverlo.



