Обработка ошибок

Этот раздел перенесён из документации Camunda 7 и в дальнейшем будет доработан с учётом особенностей OpenBPM Engine

Стратегии обработки ошибок (Error Handling Strategies)

Существует несколько базовых стратегий обработки ошибок и исключений внутри процессов. Решение о том, какую стратегию использовать, зависит от:

  • Технические vs. бизнес-ошибки: имеет ли ошибка бизнес-смысл и вызывает альтернативный поток процесса (например, «товара нет на складе») или это техническая неисправность (например, «сеть сейчас недоступна»)?

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

В контексте process engine ошибки обычно выбрасываются как Java-исключения, которые вам нужно обработать. Давайте посмотрим, как это делать.

Откаты транзакций (Transaction Rollbacks)

Стандартная стратегия обработки заключается в том, что исключения пробрасываются клиенту, то есть текущая транзакция откатывается. Это означает, что состояние процесса откатывается к последнему состоянию ожидания (wait state). Такое поведение подробно описано в разделе Transactions in Processes User Guide. Обработка ошибок движком делегируется клиенту.

Покажем это на конкретном примере: пользователь получает на фронтенде диалог об ошибке, в котором говорится, что ПО управления складом сейчас недоступно из‑за сетевых ошибок. Чтобы выполнить повторную попытку (retry), пользователю может потребоваться снова нажать ту же кнопку. Даже если это часто нежелательно, это всё равно простая стратегия, применимая во многих ситуациях.

Async и failed jobs (Async and Failed Jobs)

Если вы не хотите, чтобы исключение показывалось пользователю, один из вариантов — сделать сервисные вызовы, которые могут привести к ошибке, асинхронными (async) (как описано в Transactions in Processes). В этом случае исключение сохраняется в базе данных process engine, а фоновый Job помечается как failed (точнее: исключение сохраняется, и уменьшается некоторый счётчик повторов).

В приведённом выше примере это означает, что пользователь не увидит ошибку, а увидит диалог «всё успешно». Исключение сохранено в job. Далее либо продуманная стратегия повторов автоматически перезапустит job позже (когда сеть снова будет доступна), либо оператору нужно будет посмотреть на ошибку и запустить дополнительный retry. Далее это будет показано более подробно.

Эта стратегия довольно мощная и часто применяется в реальных проектах, однако она по-прежнему «прячет» ошибку на диаграмме BPMN. Поэтому для бизнес-ошибок, которые вы хотите видеть на диаграмме процесса, лучше использовать Error Events.

Перехватить исключение и использовать data-based XOR-gateway (Catch Exception and use Data Based XOR-Gateway)

Если вы вызываете Java-код, который может выбросить исключение, вы можете перехватить его внутри Java Delegate, CDI Bean и т. п. Возможно, уже достаточно залогировать некоторую информацию и продолжить, то есть проигнорировать ошибку. Чаще вы записываете результат в переменную процесса и позже в потоке процесса моделируете XOR-gateway, чтобы пойти по другому пути, если возникает эта ошибка.

В таком случае вы моделируете обработку ошибки явно в модели процесса, но представляете это как обычный результат, а не как ошибку. С точки зрения бизнеса это не ошибка, а результат, поэтому такое решение не стоит принимать легкомысленно. Эмпирическое правило: результаты можно обрабатывать таким образом, исключительные ошибки — нет. Однако BPMN‑перспектива не всегда должна совпадать с технической реализацией.

Пример:

Error Result XOR

Мы запускаем задачу «проверить полноту данных». Java‑сервис может выбросить DataIncompleteException. Однако, если мы проверяем полноту, неполные данные — это не исключение, а ожидаемый результат, поэтому мы предпочитаем использовать XOR-gateway в потоке процесса, который оценивает переменную процесса, например, "#{dataComplete==false}".

Error Event BPMN 2.0 (BPMN 2.0 Error Event)

Error event BPMN 2.0 даёт возможность явно моделировать ошибки, закрывая сценарий бизнес-ошибок. Наиболее показательный пример — «intermediate catching error event», который можно прикрепить к границе (boundary) активности. Определять boundary error event имеет наибольший смысл на embedded subprocess, call activity или Service Task. Ошибка приведёт к запуску альтернативного потока процесса:

Error Boundary Event

Дополнительную информацию см. в разделе Error Events справочника BPMN 2.0 Implementation Reference и в разделе Throwing Errors from Delegation Code User Guide.

Компенсация и бизнес-транзакции BPMN 2.0 (BPMN 2.0 Compensation and Business Transactions)

Транзакции и компенсации BPMN 2.0 позволяют моделировать границы бизнес-транзакций (однако не в техническом ACID‑смысле) и гарантировать, что уже выполненные действия будут компенсированы при откате. Компенсация означает сделать эффект действия невидимым, например, оприходовать товары, если вы ранее списали товары. Подробности см. в разделах BPMN Compensation event и BPMN Transaction Subprocess справочника BPMN 2.0 Implementation Reference.

Стратегии мониторинга и восстановления (Monitoring and Recovery Strategies)

Если ошибка произошла, могут применяться разные стратегии восстановления.

Разрешить пользователю повторить (Let the User Retry)

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

Повторять failed jobs (Retry Failed Jobs)

Если вы используете Jobs (async), вы можете использовать Cockpit как инструмент мониторинга для работы с failed jobs; в этом случае ни один конечный пользователь не увидит исключение. Обычно вы видите ошибки в Cockpit, когда исчерпаны повторы (retries) (подробнее см. раздел Failed Jobs документации Web Applications).

Более подробная информация — в разделе Failed Jobs in Cockpit документации Web Applications.

Если вы не хотите использовать Cockpit, вы также можете найти failed jobs через API самостоятельно:

List<Job> failedJobs = processEngine.getManagementService().createJobQuery().withException().list();
for (Job failedJob : failedJobs) {
  processEngine.getManagementService().setJobRetries(failedJob.getId(), 1);
}

Явное моделирование (Explicit Modeling)

Разумеется, вы всегда можете явно смоделировать механизм повторов, как отмечено в статье Where is the retry in BPMN 2.0:

Retry Mechanism

Мы рекомендуем ограничить это случаями, когда вы по веской причине хотите видеть это на диаграмме процесса. Мы предпочитаем асинхронное продолжение (asynchronous continuation), так как оно не раздувает диаграмму процесса и, по сути, может делать то же самое с ещё меньшими накладными расходами во время выполнения: «прохождение» через смоделированный цикл включает дополнительные действия, например, запись audit log.

User Tasks для операций (User Tasks for Operations)

Мы часто видим в проектах примерно такое:

User Task Error Handling

На самом деле это допустимый подход: вы назначаете ошибки оператору как User Tasks и моделируете, какие варианты у него есть, чтобы решить проблему. Однако это странная смесь: мы хотим обработать техническую ошибку, но добавляем её в модель бизнес-процесса. Где остановиться? Теперь нужно моделировать это на каждой Service Task?

Иметь список failed jobs вместо использования «обычного» task list кажется более естественным подходом для этой ситуации. Именно поэтому мы обычно рекомендуем другой вариант и не считаем этот подход best practice.

Коды исключений (Exception codes)

Иногда вызов API не завершается успешно из‑за возникшей проблемы. Модель программирования Java использует исключения для обработки таких ситуаций. Исключения, возникающие на уровне приложения process engine, имеют тип ProcessEngineException.

Вот два примера повседневных ситуаций, в которых движок выбрасывает ProcessEngineException:

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

  2. Два пользователя параллельно завершают одну и ту же задачу.

Вы можете прочитать сообщение исключения, чтобы понять причину ProcessEngineException. Однако иногда сообщение исключения верхнего уровня слишком общее. В таких ситуациях причина (cause) может содержать более информативное сообщение исключения. Проходить по цепочке причин исключения может быть утомительно. Кроме того, причины недоступны, когда ошибка возникает на уровне REST API.

Хотя чтение сообщения об ошибке может помочь пользователям понять первопричину проблемы, автоматически анализировать (evaluate) сообщения исключений — плохая идея, потому что:

  • Сообщение может измениться в более новых версиях.

  • Опора на фрагменты сообщения может приводить к ошибкам.

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

Вы можете получать коды ошибок как через Java, так и через REST API.

Встроенные коды (Built-in codes)

Мы выделили распространённые ситуации, в которых движок выбрасывает исключение, и назначили исключению встроенный код ошибки. Найти встроенные коды можно в разделе Categories, ranges, and codes #_categories_ranges_and_codes.

Пользовательские коды (Custom codes)

Иногда вам может потребоваться назначить коды для специфических ошибок, которые OpenBPM Engine пока не покрыла. Вы можете либо определить пользовательские коды из delegation code, либо зарегистрировать ваш собственный ExceptionCodeProvider #_register_a_custom_code_provider.

Delegation code

Подробнее о том, как назначать пользовательский код ошибки исключению, см. в документации по Delegation Code.

Конфигурация (Configuration)

Вы можете настроить функциональность кодов исключений в вашей конфигурации process engine:

  • Чтобы полностью отключить функциональность кодов исключений, установите флаг disableExceptionCode в конфигурации process engine в значение true.

  • Чтобы отключить встроенного провайдера кодов исключений, установите флаг disableBuiltinExceptionCodeProvider в конфигурации process engine в значение true. Отключение встроенного провайдера кодов исключений позволяет переопределять зарезервированный диапазон кодов вашими пользовательскими кодами исключений.

Регистрация пользовательского провайдера кодов (Register a Custom Code Provider)

С помощью ProcessEnginePlugin вы можете зарегистрировать пользовательский ExceptionCodeProvider:

engineConfig.setCustomExceptionCodeProvider(new ExceptionCodeProvider() {

  @Override
  public Integer provideCode(ProcessEngineException processEngineException) {
    // Put your business logic here to determine the
    // error code in case a process engine exception was thrown.
    return 22_222;
  }

  @Override
  public Integer provideCode(SQLException sqlException) {
    // Put your business logic here to determine the
    // error code in case a sql exception was thrown.
    return 33_333;
  }

});

Если ваш пользовательский код ошибки нарушает зарезервированный диапазон кодов, он будет переопределён значением 0, если вы не отключите встроенного провайдера кодов.

Категории, диапазоны и коды (Categories, ranges, and codes)

В таблице ниже приведён обзор всех категорий, диапазонов и кодов:

Category Range Code Description Safe to retry

Fallback

0

Все ошибки, которым не назначен код.

Engine

[1, 9999]

1

OptimisticLockingException

X

Persistence

[10000, 19999]

10,000

Произошла ситуация deadlock.

X

10,001

Нарушено ограничение внешнего ключа (foreign key constraint).

10,002

Размер колонки слишком мал.

Custom

[20000, 39999]

<i>E.g., 22,222</i>

<i>E.g., custom JavaDelegate validation error.</i>

Зарезервированный диапазон кодов (Reserved code range)

Коды $⇐ 19,999$ и $>= 40,000$ зарезервированы для встроенных кодов. Если вы отключите встроенного провайдера кодов, вы также сможете использовать зарезервированный диапазон кодов для ваших пользовательских кодов.

Лицензия и атрибуция

Эта документация была создана на базе материала "Camunda 7 Docs" от Camunda, находится под лицензией Creative Commons Attribution-ShareAlike 3.0 Unported License .

Оригинал документации: https://docs.camunda.org