Un processo aziendale può completare una parte del proprio lavoro e fallire prima di terminare. Per esempio, un'applicazione PHP crea un account nel proprio database, ma non riesce a predisporne l'attivazione in un servizio esterno. A quel punto, ripetere semplicemente il flusso può duplicare gli effetti; considerarlo fallito può lasciare i sistemi in uno stato incoerente. Le compensazioni nei processi distribuiti con PHP consentono di definire come rispondere a questi risultati parziali, senza presupporre che più sistemi partecipino a un'unica transazione atomica.
La decisione importante non riguarda solo come annullare ciò che è accaduto. Alcuni effetti non possono essere annullati esattamente e anche una compensazione può fallire. La progettazione deve rendere visibile lo stato reale dell'operazione, consentirne il recupero e indicare quando è necessario l'intervento umano.
Transazioni locali, tentativi ripetuti e compensazioni risolvono problemi diversi

Una transazione del database protegge le modifiche entro i confini di quel database. Se il processo salva più righe e un'operazione fallisce prima del COMMIT, può eseguire un ROLLBACK per annullare tali modifiche. Ma questo rollback non ritira un'email inviata, non annulla automaticamente una richiesta già accettata da un'API e non ripristina le modifiche confermate in un altro database.
Un tentativo ripetuto riesegue un'azione che potrebbe essere fallita a causa di un problema temporaneo, come un timeout o un'indisponibilità. È appropriato solo se si conoscono le conseguenze della ripetizione. La richiesta potrebbe essere arrivata al sistema esterno anche se la risposta è andata persa; senza una chiave di idempotenza o una verifica dello stato, il nuovo tentativo potrebbe creare un duplicato.
Una compensazione esegue un'azione aziendale che controbilancia un passaggio già completato: per esempio, disattivare un accesso appena creato. Non è un rollback tecnico e non garantisce il ripristino esatto dello stato precedente. Una riconciliazione, invece, confronta quanto registrato localmente con lo stato dei sistemi coinvolti e aiuta a rilevare e risolvere le discrepanze. Può essere necessaria anche quando si utilizzano tentativi ripetuti e compensazioni.
Modella ogni operazione come una sequenza di passaggi osservabili
Prima di scrivere gestori delle eccezioni, descrivi il flusso come una serie di passaggi indipendenti. Per ciascuno, specifica quale sistema modifica, quale risultato è considerato un successo, come viene identificata l'operazione, se è sicuro ripeterla e quale alternativa è disponibile se non può essere completata. Definisci anche quali effetti devono restare coerenti: questa regola aziendale è l'invariante che guiderà il recupero.
Rendi persistenti nell'applicazione un registro dell'operazione e dei relativi passaggi. Un modello semplice può includere un identificatore stabile dell'operazione, lo stato generale, il nome del passaggio, il risultato, i tentativi, i timestamp e l'ultimo errore rilevante. Stati come in attesa, in corso, completato, non riuscito, in compensazione e richiede revisione aiutano a distinguere ciò che è noto da ciò che non è ancora stato confermato.
Salva la modifica locale e il record del lavoro in modo atomico quando devono rimanere collegati. Una coda di lavoro può elaborare in seguito la chiamata esterna; il pattern scatola esterna è un'opzione per evitare che la modifica nel database venga confermata mentre si perde la notifica che avrebbe dovuto avviare il passaggio successivo. Non elimina i problemi di rete: il consumer deve comunque tollerare messaggi ripetuti e risultati incerti.
Evita di mantenere aperta una transazione del database durante una chiamata di rete. Le chiamate esterne hanno latenza e possono bloccarsi, mentre una transazione prolungata trattiene risorse e non rende atomici i due sistemi. Conferma il lavoro locale seguendo una sequenza esplicita e registra ciò che serve per proseguire o compensare.
Progetta compensazioni esplicite, circoscritte e idempotenti
Per ogni passaggio che produce un effetto, decidi se esiste una compensazione e quando deve essere eseguita. La compensazione non deve necessariamente essere l'inverso letterale dell'azione. Se un account ha già ricevuto una notifica, non è possibile cancellare il fatto che il messaggio sia stato letto; può essere opportuno registrare una rettifica o comunicare l'annullamento. Se un'azione è irreversibile o ha conseguenze legali o finanziarie, la risposta corretta potrebbe essere arrestare il flusso e sottoporlo a escalation.
- Idempotenza: eseguire due volte la stessa compensazione non deve creare effetti aggiuntivi. Usa un identificatore stabile dell'operazione o una chiave idempotente, se il sistema lo consente, e registra il risultato.
- Ordine: definisci quali passaggi compensare e in quale sequenza. Di solito si tenta di annullare le operazioni nell'ordine inverso a quello di esecuzione, ma le dipendenze e le regole aziendali possono richiedere un ordine diverso.
- Limiti: stabilisci quali errori consentono nuovi tentativi, quanti tentativi effettuare e quando fermarsi. Prevedi intervalli tra i tentativi ed evita di riprovare indefinitamente.
- Incertezza: se scade il timeout di un'API, verifica lo stato o usa una chiave di idempotenza prima di ripetere una creazione o eseguire una compensazione potenzialmente errata.
La logica di compensazione deve essere un'operazione aziendale identificabile, non un blocco generico che elimina dati quando intercetta una qualsiasi eccezione. Conserva lo storico necessario per spiegare quanto è accaduto e verificare che la risposta rispetti le regole del prodotto.
Esempio ipotetico: creazione di un account in due sistemi
Supponiamo che un flusso crei un account locale e chieda a un provider esterno di attivare uno spazio di lavoro. L'applicazione assegna un identificatore all'operazione e registra la creazione come in attesa. In una transazione locale salva l'account e un'attività per richiedere il provisioning. Un worker PHP elabora l'attività e registra l'identificatore esterno e il risultato della richiesta.
Se il provider conferma l'attivazione, il flusso contrassegna l'operazione come completata. Se risponde con un errore temporaneo noto, l'attività può essere riprovata entro limiti definiti e con la stessa chiave idempotente. Se la risposta è incerta, l'applicazione cerca innanzitutto di verificare se lo spazio di lavoro esiste. Se il provider conferma che è stato creato, ma un requisito locale successivo impedisce di completare la creazione dell'account, il sistema può richiederne la disattivazione come compensazione e registrarne l'esito.
Se la disattivazione fallisce, non si deve dichiarare che la creazione dell'account è stata annullata. L'operazione resta in uno stato visibile di recupero in attesa o di revisione, con gli identificatori locale ed esterno, i tentativi e l'errore. Il team operativo ha bisogno di una procedura per verificare il provider, decidere quale azione è consentita e registrarne l'esito. L'esempio è ipotetico: le azioni concrete dipendono dalle funzionalità dell'API e dalle regole aziendali.
Quando fallisce anche la compensazione: visibilità e revisione
Un flusso distribuito non può garantire che ogni azione venga annullata automaticamente. Progetta una procedura esplicita per l'esaurimento dei tentativi, una risposta esterna ambigua o il fallimento della compensazione. Conserva lo stato senza nascondere l'incoerenza ed evita di presentare l'operazione all'utente come completata se non lo è ancora.
Metti a disposizione del supporto o del team operativo una vista che mostri lo stato di ogni passaggio, l'ultimo errore, gli identificatori di correlazione e le azioni consentite. La revisione umana deve prevedere controlli di accesso e lasciare una traccia di audit della decisione. Esponi metriche e avvisi per rilevare le operazioni bloccate, le compensazioni non riuscite e le discrepanze che superano il tempo operativo accettabile. Un'attività periodica di riconciliazione può confrontare i record locali con il sistema esterno e aprire casi per risolvere le differenze.
Verifica i guasti parziali e decidi se serve una compensazione

I test devono coprire più del solo percorso di successo. Simula errori prima e dopo ogni effetto, risposte tardive, timeout con esito sconosciuto, messaggi duplicati, processi interrotti e guasti durante la compensazione. Verifica lo stato persistito dopo il riavvio del worker e conferma che i nuovi tentativi non duplicano account né altre operazioni.
- L'identificatore dell'operazione e i risultati di ogni passaggio vengono conservati?
- Un messaggio ripetuto produce lo stesso risultato senza effetti duplicati?
- Si verifica lo stato esterno quando la risposta a una richiesta è incerta?
- La compensazione viene eseguita nell'ordine previsto ed è sicura se ripetuta?
- Un errore di compensazione lascia l'operazione visibile e la inoltra al processo di revisione?
- È possibile riconciliare in seguito lo stato locale con il sistema esterno?
- I test verificano gli invarianti aziendali, non solo i codici di risposta?
Se il flusso coinvolge un solo sistema e una transazione locale protegge tutte le modifiche, quella transazione e un semplice meccanismo di recupero possono essere sufficienti. Se sono presenti effetti esterni ripetibili con garanzie note, potrebbero bastare tentativi ripetuti idempotenti e riconciliazione. Quando un passaggio confermato deve essere controbilanciato per preservare una regola aziendale, definisci una compensazione. La complessità aggiuntiva vale la pena quando il costo di lasciare inosservati stati parziali supera quello di mantenere il registro, i test e la procedura operativa.



