Domanda

Qual è il modo migliore per eseguire un paio di attività insieme e se un'attività fallisce, le attività successive non devono essere completate? So che se fossero le operazioni del database avrei dovuto usare Transazioni ma sto parlando di diversi tipi di operazioni come la seguente:

Tutte le attività devono essere passate:

SendEmail ArchiveReportsInDatabase CreateAFile

Nello scenario sopra riportato tutte le attività devono passare altrimenti l'intera operazione batch deve essere ripristinata.

È stato utile?

Soluzione

Le eccezioni sono generalmente buone per questo genere di cose. Codice pseudo-Java / JavaScript / C ++:

try {
    if (!SendEmail()) {
        throw "Could not send e-mail";
    }

    if (!ArchiveReportsInDatabase()) {
        throw "Could not archive reports in database";
    }

    if (!CreateAFile()) {
        throw "Could not create file";
    }

    ...

} catch (Exception) {
    LogError(Exception);
    ...
}

Meglio ancora se i tuoi metodi generano eccezioni:

try {
    SendEmail();
    ArchiveReportsInDatabase();
    CreateAFile();
    ...

} catch (Exception) {
    LogError(Exception);
    ...
}

Un risultato molto piacevole di questo stile è che il tuo codice non viene indentato sempre di più man mano che avanzi nella catena di attività; tutte le chiamate al metodo rimangono allo stesso livello di rientro. Troppa indentazione rende il codice più difficile da leggere.

Inoltre, nel codice è presente un unico punto per la gestione degli errori, la registrazione, il rollback ecc.

Altri suggerimenti

I rollback sono difficili: AFAIK, ci sono solo 2 modi per farlo. Un protocollo di commit a 2 fasi oppure transazioni compensative . Devi davvero trovare un modo per strutturare i tuoi compiti in una di queste mode.

Di solito, l'idea migliore è quella di trarre vantaggio dal duro lavoro di altre persone e utilizzare tecnologie che hanno già 2PC o compensazione incorporati. Questo è uno dei motivi per cui RDBMS è così popolare.

Quindi, le specifiche dipendono dal compito ... ma il modello è abbastanza semplice:

class Compensator {
   Action Action { get; set; }
   Action Compensate { get; set; }
}

Queue<Compensator> actions = new Queue<Compensator>(new Compensator[] { 
   new Compensator(SendEmail, UndoSendEmail),
   new Compensator(ArchiveReportsInDatabase, UndoArchiveReportsInDatabase),
   new Compensator(CreateAFile, UndoCreateAFile)
});

Queue<Compensator> doneActions = new Queue<Compensator>();
while (var c = actions.Dequeue() != null) {
   try {
      c.Action();
      doneActions.Add(c);
   } catch {
      try {
        doneActions.Each(d => d.Compensate());
      } catch (EXception ex) {
        throw new OhCrapException("Couldn't rollback", doneActions, ex);
      }
      throw;
   }
}

Naturalmente, per i tuoi compiti specifici, potresti essere fortunato.

  • Ovviamente, il lavoro RDBMS può già essere racchiuso in una transazione.
  • Se utilizzi Vista o Server 2008, otterrai NTFS transazionale per la copertura il tuo scenario CreateFile.
  • L'email è un po 'più complicata - non conosco nessun 2PC o compensatori intorno (sarei solo leggermente sorpreso se qualcuno avesse sottolineato che Exchange ne ha uno, però) quindi probabilmente userò MSMQ per scrivere una notifica e consentire a un abbonato di prenderla e infine inviarla via email. A quel punto, la tua transazione copre davvero solo l'invio del messaggio alla coda, ma probabilmente è abbastanza buono.

Tutti questi possono partecipare a una System.Transactions , quindi dovresti essere in ottima forma.

in C #

restituisce SendEmail () & amp; & amp; ArchiveResportsInDatabase () & amp; & amp; CreateAFile ();

Un'altra idea:

try {
    task1();
    task2();
    task3();
    ...
    taskN();
}
catch (TaskFailureException e) {
    dealWith(e);
}

Un paio di suggerimenti:

In uno scenario distribuito, potrebbe essere necessaria una sorta di protocollo di commit in due fasi. In sostanza, invii a tutti i partecipanti un messaggio che dice "Preparati a fare X". Ogni partecipante deve quindi inviare una risposta dicendo "OK, garantisco che posso fare X" o " No, non posso farlo. " Se tutti i partecipanti garantiscono che possono completare, quindi inviare il messaggio dicendo loro di farlo. Le "garanzie" può essere rigoroso quanto necessario.

Un altro approccio è fornire una sorta di meccanismo di annullamento per ogni operazione, quindi avere una logica come questa:

try:
    SendEmail()
    try:
        ArchiveReportsInDatabase()
        try:
             CreateAFile()
        except:
            UndoArchiveReportsInDatabase()
            raise
    except:
        UndoSendEmail()
        raise
except:
    // handle failure

(Non vorrai che il tuo codice fosse simile a questo; questo è solo un esempio di come dovrebbe fluire la logica.)

Se la tua lingua lo consente, è molto ordinato:

  1. Inserisci le tue attività in una matrice di blocchi di codice o puntatori a funzione.
  2. Scorre sull'array.
  3. Rompi se un blocco restituisce un errore.

Non hai menzionato il linguaggio / ambiente di programmazione che stai utilizzando. Se si tratta di .NET Framework, potresti dare un'occhiata a questo articolo . Descrive il Runtime di concorrenza e controllo di Robotics Studio di Microsoft, che consente di applicare tutti i tipi di regole su una serie di eventi (asincroni): ad esempio, è possibile attendere il completamento di un numero qualsiasi di essi, annullare se un evento fallisce, ecc. Può anche eseguire cose in più thread, in modo da ottenere un metodo molto potente per fare cose.

Non specifichi il tuo ambiente. Nella shell scripting Unix, & amp; & amp; l'operatore fa proprio questo.

SendEmail () {
  # ...
}
ArchiveReportsInDatabase () {
  # ...
}
CreateAFile () {
  # ...
}

SendEmail && ArchiveReportsInDatabase && CreateAFile

Se stai usando una lingua che utilizza valutazione del circuito di ordinamento (Java e C # do), puoi semplicemente fare:

return SendEmail() && ArchiveResportsInDatabase() && CreateAFile();

Questo restituirà vero se tutte le funzioni ritornano vero e si fermerà non appena il primo restituirà falso.

Per farlo davvero bene dovresti usare un modello di messaggistica asincrono. Ho appena finito un progetto in cui l'ho fatto usando nServiceBus e MSMQ.

Fondamentalmente, ogni passaggio avviene inviando un messaggio a una coda. Quando nServiceBus trova messaggi in attesa nella coda, chiama il tuo metodo Handle corrispondente a quel tipo di messaggio. In questo modo ogni singolo passaggio è disponibile e riprovabile indipendentemente. Se un passaggio ha esito negativo, il messaggio finisce in una coda di errori in modo da poterlo riprovare facilmente in seguito.

Queste soluzioni di codice puro suggerite non sono così solide poiché se un passaggio fallisce non avresti modo di riprovare solo quel passaggio in futuro e dovresti implementare il codice di rollback che non è nemmeno possibile in alcuni casi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top