È sempre possibile arrivare a un caso ancora peggiore che rovini qualsiasi soluzione tu possa inventare. Assumi un servizio Web ostile o un database ostile e sei fregato.
Ma nello scenario che descrivi, ti serve principalmente un registro delle azioni più affidabile di un database esterno. Supponiamo che il file system locale sia affidabile se non si aumentano le dimensioni del file (che potrebbe facilmente fallire se il disco è pieno).
Quindi il programma dovrebbe, all'avvio, creare un piccolo file che contenga solo due record di dimensione fissa, ciascuno formato da uno spazio per un ID e un flag di stato. Inoltre, cambiamo il database in qualcosa di leggermente più elaborato: la bandiera elaborata ora può essere "nuova", "in corso" e "elaborata".
Allora vuoi qualcosa come questo pseudocodice:
txlog = disk struct { id, status }
record = db -> load record
txlog -> id = record.id, status = sending
db -> processed = in progress, on failure txlog -> clear and start over
web -> send request, on error response goto service error, on timeout abort
txlog -> status = sent
db -> processed = done, on failure abort
txlog -> clear
Il gestore degli errori di servizio ha il seguente aspetto:
txlog -> status = failed
db -> processed = new, on failure abort
txlog -> clear
Ora, la parte interessante viene dopo. Se si verifica un errore di db e si interrompe, o il programma scompare improvvisamente (interruzione dell'alimentazione), o se non si ottiene una risposta dal servizio Web (arresto del servizio Web o perdita di connettività), il programma ricomincerà alla fine e realizzerà che il txlog il file non è cancellato. Quindi deve entrare in modalità di ripristino degli errori.
Se il txlog ha lo stato "invio", controlla il record db. Se questo ha lo stato "nuovo", il tuo programma è morto prima che potesse inviare la richiesta. Cancella il log e procedi normalmente.
Se il record db ha lo stato "in corso", sei nei guai: significa che qualcosa è andato storto in un punto in cui potrebbe aver inviato la richiesta al servizio web e > potrebbe essere stato elaborato. Forse hai perso alimentazione o rete, o il servizio web, ma non puoi sapere se la richiesta è andata a buon fine. In questo caso, puoi solo avvisare un operatore umano di dare un'occhiata. Ne parlerò più tardi.
Se il txlog ha stato "inviato", il record è stato inviato. Se il db ha ancora lo stato "in corso", prova a reimpostare lo stato. In caso contrario, il programma è morto prima che potesse cancellare il txlog, oppure il db ha fatto il suo aggiornamento ma è scomparso senza inviare un messaggio di successo. In ogni caso, cancella il registro e prosegui.
Ma per quanto riguarda il caso di errore? Questo è il "caso peggiore" di cui ho parlato all'inizio, il vero problema del sistema inaffidabile con cui hai a che fare. Perdita di potenza nel momento sbagliato. Un cavo di rete che viene improvvisamente tagliato. Il tuo sistema deve adattarsi, e se, come nel caso che stai descrivendo, non può farlo (il servizio web che descrivi semplicemente non dà quell'opzione), sei fregato se accade il caso peggiore. Il processo che ho descritto ti avviserà almeno di ciò. L'operatore umano può quindi contattare gli amministratori del servizio Web per chiedere informazioni sui propri registri o, in caso contrario, prendere una decisione informata sulle operazioni da eseguire o meno.
Da una nota a margine, potremmo progettare, ad esempio, un servizio di messaggi di testo che sia idempotente? Sicuro. Il servizio richiederebbe, come primo passo in ogni processo, l'assegnazione di un GUID per il messaggio da inviare. Una volta ottenuto tale GUID, è possibile memorizzarlo nel registro delle transazioni. Quindi si invia il messaggio con il GUID. Il servizio può quindi verificare i propri record e scoprire che il messaggio con questo GUID è già stato inviato e quindi non inviarlo di nuovo. E anche se il servizio web non registra correttamente il fatto che ha inviato il messaggio, il ricevitore finale può fare lo stesso: il telefono, ad esempio, potrebbe vedere che ha già ricevuto un messaggio con quel GUID e ignora il nuovo.