Pregunta

¿Cuál es la mejor manera de realizar un par de tareas juntas y si una tarea falla, las siguientes tareas no deben completarse? Sé que si se tratara de las operaciones de la base de datos, debería haber usado Transacciones, pero estoy hablando de diferentes tipos de operaciones como las siguientes:

Todas las tareas deben pasar:

Enviar correo electrónico ArchiveReportsInDatabase CreateAFile

En el escenario anterior, todas las tareas deben pasar o, de lo contrario, toda la operación por lotes debe revertirse.

¿Fue útil?

Solución

Las excepciones son generalmente buenas para este tipo de cosas. Código 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);
    ...
}

Mejor aún si sus métodos lanzan excepciones ellos mismos:

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

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

Un resultado muy bueno de este estilo es que su código no se sangra cada vez más a medida que avanza en la cadena de tareas; todas sus llamadas de método permanecen en el mismo nivel de sangría. Demasiada sangría hace que el código sea más difícil de leer.

Además, tiene un único punto en el código para el manejo de errores, el registro, la reversión, etc.

Otros consejos

Los rollbacks son difíciles: AFAIK, solo hay 2 formas de hacerlo. Ya sea un protocolo de confirmación de dos fases , o compensar transacciones . Realmente tienes que encontrar una manera de estructurar tus tareas de una de estas maneras.

Por lo general, la mejor idea es aprovechar el trabajo duro de otras personas y usar tecnologías que ya tienen 2PC o compensación incorporada. Esa es una de las razones por las que los RDBMS son tan populares.

Por lo tanto, los detalles dependen de la tarea ... pero el patrón es bastante sencillo:

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;
   }
}

Por supuesto, para tus tareas específicas, puedes tener suerte.

  • Obviamente, el trabajo RDBMS ya puede estar envuelto en una transacción.
  • Si está en Vista o Server 2008, entonces obtiene NTFS transaccional para cubrir su escenario CreateFile.
  • El correo electrónico es un poco más complicado. No sé de ningún 2PC o Compensadores que lo rodeen (solo me sorprendería un poco si alguien señalara que Exchange tiene uno), así que probablemente use MSMQ para escribir una notificación y dejar que un suscriptor la recoja y finalmente la envíe por correo electrónico. En ese momento, su transacción realmente cubre solo el envío del mensaje a la cola, pero probablemente sea lo suficientemente bueno.

Todos estos pueden participar en un System.Transactions Transacción , por lo que debería estar en muy buena forma.

en C #

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

Otra idea:

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

Un par de sugerencias:

En un escenario distribuido, puede ser necesario algún tipo de protocolo de confirmación de dos fases. Esencialmente, envía a todos los participantes un mensaje que dice "Prepárese para hacer X". Cada participante debe enviar una respuesta que diga "OK, garantizo que puedo hacer X" o " No, no puedo hacerlo. " Si todos los participantes garantizan que pueden completar, envíe el mensaje para que lo hagan. Las " garantías " puede ser tan estricto como sea necesario.

Otro enfoque es proporcionar algún tipo de mecanismo de deshacer para cada operación, luego tener una lógica como esta:

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

(No querrías que tu código se viera así; esto es solo una ilustración de cómo debería fluir la lógica).

Si su idioma lo permite, esto está muy ordenado:

  1. Coloque sus tareas en una matriz de bloques de código o punteros de función.
  2. Iterar sobre la matriz.
  3. Rompe si algún bloque devuelve un error.

No mencionaste qué lenguaje de programación / entorno estás usando. Si se trata de .NET Framework, puede consultar este artículo . Describe el Tiempo de ejecución de control y concurrencia de Robotics Studio de Microsoft, que le permite aplicar todo tipo de reglas en un conjunto de eventos (asíncronos): por ejemplo, puede esperar que se complete cualquier número de ellas, cancelarlas si falla un evento, También puede ejecutar cosas en varios subprocesos, así que obtienes un método muy poderoso para hacer cosas.

No especificas tu entorno. En los scripts de shell de Unix, el & amp; & amp; el operador hace justamente esto.

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

SendEmail && ArchiveReportsInDatabase && CreateAFile

Si está utilizando un lenguaje que utiliza evaluación del circuito de clasificación (Java y C # do), simplemente puede hacer:

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

Esto devolverá verdadero si todas las funciones devuelven verdadero, y se detendrá tan pronto como el primero devuelva falso.

Para hacerlo bien, debes usar un patrón de mensajería asíncrono. Acabo de terminar un proyecto en el que hice esto utilizando nServiceBus y MSMQ.

Básicamente, cada paso sucede al enviar un mensaje a una cola. Cuando nServiceBus encuentra mensajes en espera en la cola, llama a su método Handle correspondiente a ese tipo de mensaje. De esta manera, cada paso individual es de forma independiente y se puede volver a intentar. Si un paso falla, el mensaje termina en una cola de errores para que pueda volver a intentarlo más tarde.

Estas soluciones de código puro que se sugieren no son tan sólidas, ya que si un paso falla, no tendría forma de volver a intentar ese único paso en el futuro y tendría que implementar un código de reversión que ni siquiera es posible en algunos casos.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top