Лучшие практики для выполнения последовательных операций - PullRequest
3 голосов
/ 03 октября 2008

Каков наилучший способ выполнить пару задач вместе, и если одна задача не выполняется, то следующие задачи не должны быть выполнены? Я знаю, если бы это были операции с базой данных, то я должен был бы использовать транзакции, но я говорю о различных типах операций, таких как:

Все задания должны пройти:

SendEmail ArchiveReportsInDatabase CreateAFile

В приведенном выше сценарии все задачи должны пройти, иначе вся пакетная операция должна быть откатом.

Ответы [ 10 ]

4 голосов
/ 04 октября 2008

Откаты сложны - AFAIK, на самом деле есть только 2 способа сделать это. Либо 2-фазный протокол фиксации , либо компенсирующие транзакции . Вы действительно должны найти способ структурировать свои задачи в одном из этих способов.

Как правило, лучшей идеей является использование преимуществ тяжелой работы других людей и использование технологий, в которых уже есть 2PC или встроенная компенсация. Это одна из причин, по которой СУБД так популярны.

Итак, специфика зависит от задачи ... но шаблон довольно прост:

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

Конечно, для ваших конкретных задач - вам может повезти.

  • Очевидно, что работа СУБД уже может быть включена в транзакцию.
  • Если вы работаете в Vista или Server 2008, вы получаете Транзакционная NTFS для покрытия вашего сценария CreateFile.
  • Электронная почта немного сложнее - я не знаю каких-либо 2PC или компенсаторов вокруг нее (хотя я был бы немного удивлен, если бы кто-то указал, что у Exchange есть такой), поэтому я бы, вероятно, использовал MSMQ написать уведомление и позволить абоненту забрать его и в конечном итоге отправить его по электронной почте. В этот момент ваша транзакция действительно охватывает только отправку сообщения в очередь, но, вероятно, этого достаточно.

Все они могут участвовать в транзакции System.Transactions , поэтому вы должны быть в хорошей форме.

2 голосов
/ 03 октября 2008

в C #

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

1 голос
/ 03 октября 2008

Пара предложений:

В распределенном сценарии может потребоваться некоторый протокол двухфазной фиксации. По сути, вы отправляете всем участникам сообщение с надписью «Приготовьтесь к X». Затем каждый участник должен отправить ответ: «ОК, я гарантирую, что могу сделать Х» или «Нет, я не могу». Если все участники гарантируют, что они могут завершить, отправьте сообщение с указанием сделать это. «Гарантии» могут быть настолько строгими, насколько это необходимо.

Другой подход заключается в том, чтобы предоставить какой-то механизм отмены для каждой операции, а затем использовать такую ​​логику:

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

(Вы не хотели бы, чтобы ваш код выглядел так; это просто иллюстрация того, как должна протекать логика.)

1 голос
/ 03 октября 2008

Другая идея:

try {
    task1();
    task2();
    task3();
    ...
    taskN();
}
catch (TaskFailureException e) {
    dealWith(e);
}
0 голосов
/ 04 октября 2008

Чтобы действительно сделать это правильно, вы должны использовать асинхронный шаблон обмена сообщениями. Я только что закончил проект, где я сделал это, используя nServiceBus и MSMQ.

По сути, каждый шаг происходит путем отправки сообщения в очередь. Когда nServiceBus находит сообщения, ожидающие в очереди, он вызывает ваш метод Handle, соответствующий этому типу сообщения. Таким образом, каждый отдельный шаг может быть неудачным и повторным. Если один шаг завершается неудачно, сообщение попадает в очередь ошибок, поэтому вы можете легко повторить попытку позже.

Предлагаемые решения с чистым кодом не так надежны, поскольку в случае сбоя шага у вас не будет возможности повторить попытку только этого шага в будущем, и вам придется реализовать код отката, который даже невозможен некоторые случаи.

0 голосов
/ 04 октября 2008

Исключения обычно хороши для такого рода вещей. Псевдо-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);
    ...
}

Еще лучше, если ваши методы сами генерируют исключения:

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

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

Очень хорошим результатом этого стиля является то, что ваш код не становится все более отступающим по мере продвижения по цепочке задач; все ваши вызовы методов остаются на том же уровне отступа. Слишком много отступов затрудняет чтение кода.

Более того, у вас есть единственная точка в коде для обработки ошибок, регистрации, отката и т. Д.

0 голосов
/ 03 октября 2008

Если вы используете язык, который использует оценку схемы сортировки (Java и C # do), вы можете просто сделать:

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

Это вернет true, если все функции вернут true, и остановится, как только первая вернет false.

0 голосов
/ 03 октября 2008

Вы не указываете свою среду. В сценариях оболочки Unix оператор && делает именно это.

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

SendEmail && ArchiveReportsInDatabase && CreateAFile
0 голосов
/ 03 октября 2008

Вы не упомянули, какой язык программирования / среду вы используете. Если это .NET Framework, возможно, вы захотите взглянуть на эту статью . В нем описывается среда выполнения параллелизма и управления из Robotics Studio от Microsoft, которая позволяет применять все виды правил к набору (асинхронных) событий: например, вы можете подождать, пока завершится любое их количество, отменить при сбое одного события, и т.д. Он также может запускать вещи в нескольких потоках, так что вы получаете очень мощный метод выполнения вещей.

0 голосов
/ 03 октября 2008

Если ваш язык позволяет, это очень аккуратно:

  1. Поместите свои задачи в массив блоков кода или указателей на функции.
  2. Итерация по массиву.
  3. Перерыв, если какой-либо блок возвращает ошибку.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...