Исключения являются частью любого процесса. Разработчики, создающие бизнес-процессы, должны уметь обрабатывать исключения в бизнес-кейсе, чтобы гарантировать, что сам процесс устойчив и может продолжаться после возникновения сбоев. Логика обработки исключений различается в зависимости от вашей задачи и инструментов, которые у вас есть в распоряжении. В этой заметке я попытался определить различные методы обработки исключений, используя язык паттернов. Каждый паттерн следует определённой структуре, называемой Контекст (общая ситуация, в которой проблема повторяется), Проблема (формулировка), Условия (условия, при которых можно рекомендовать предложенные решения) и Решение. Итак, давайте приступим.
1. XOR или Исключающий шлюз (Exclusive Gateway)
Контекст
Вам поручено создать рабочий процесс.
Проблема
Вы хотите обработать нежелательный (неудачный) сценарий в рабочем процессе.
Условия
Вы хотите использовать простой подход для моделирования нежелательного сценария.
Ваш инструмент не полностью соответствует спецификации BPMN 2.0.
Решение
BPMN сервисные задачи позволяют выполнять пользовательский Java-код. При выполнении вы можете перехватывать ошибки и устанавливать некоторые переменные. Затем эти переменные можно использовать в BPMN-диаграмме для моделирования потока. Стоит отметить, что это не полноценная обработка исключений, а моделирование потока на основе значений переменных результата. Для XOR-шлюза можно использовать выражение типа ${IS_SUCCESSFUL == false}
.

Сервисная задача «Validate Data» выполняет следующий Java-делегат:
package delegate;
import org.flowable.engine.delegate.BpmnError;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
/**
* @author im-pratham
**/
public class ErrorSettingDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
Validator validator = new FormFieldValidator();
boolean result = validator.validate(delegateExecution);
delegateExecution.setVariable("IS_SUCCESSFUL", result);
}
}
Кстати, этот XOR таит много секретов!
Читайте о нем специальную статью Такой простой Exclusive Gateway
Альтернативные решения
Как уже упоминалось, это не корректный способ обработки исключений. Если вы хотите использовать более соответствующий спецификации BPMN подход, обратитесь к приведённым ниже решениям.
2. Граничное событие ошибки (Error Boundary Event)
Спецификация BPMN 2.0 добавляет возможность перехвата событий ошибок и запуска пользовательского процесса. Мы подробно рассмотрим их ниже.

Контекст
Вам поручено создать рабочий процесс.
Проблема
Вы хотите обработать нежелательный сценарий в рабочем процессе и перенаправить поток выполнения в зависимости от события.
Условия
Ваш инструмент соответствует спецификации BPMN 2.0.
Вы хотите обрабатывать несколько сценариев ошибок, возникающих в контексте конкретной активности или процесса.
Решение
Граничное событие (boundary event) прикрепляется к границе активности или подпроцесса и может быть вызвано во время выполнения связанной задачи. Как следует из названия, граничное событие ошибки (Error Boundary Event) отслеживает любые ошибки, возникающие в связанной активности, и если код ошибки совпадает с его определением, оно изменяет поток выполнения и начинает выполнять связанный с ним поток.

В приведённом процессе контактные данные получаются путем вызова какого-либо сервиса или REST API. Пользователь выбирает контакт, которому он/она хочет отправить письмо. Сервисная задача «Prepare for Mail» проверяет введённые данные, проверяет, не пустой ли адрес электронной почты, и если это так, выбрасывает исключение BpmnError. Я прикрепил граничное событие ошибки с кодом ошибки WRONG_EMAIL_DETAILS
, который совпадает с кодом, выбрасываемым из нашего Java-кода ниже:
package delegate;
import org.flowable.engine.delegate.BpmnError;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.apache.commons.lang.StringUtils;
/**
* @author im-pratham
**/
public class ErrorThrowingDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
if (StringUtils.isEmpty(delegateExecution.getVariable("emailId"))) {
// attach error boundary event with error code = WRONG_EMAIL_DETAILS, to capture it.
throw new BpmnError("WRONG_EMAIL_DETAILS", "given emails address is invalid.");
}
else {
new MailService().sendEmail(delegateExecution);
}
}
}
view raw
Вы можете увидеть похожий паттерн, используемый в реализации Service Registry —
Service Registry — это фича Flowable Enterprise, которая позволяет вызывать любой REST API или Java-бины с помощью удобного для проектировщика процессов интерфейса.

В приведённом выше рабочем процессе задача «Call HTTP Service» представляет собой сервисный реестр, который вызывает REST-эндпоинт. Service Registry позволяет обрабатывать HTTP-коды состояния с помощью свойства под названием «Handle status codes» (Обрабатывать коды состояния). Вы можете указать HTTP-коды, которые хотите обрабатывать, например, 404. Когда Flowable встречает такую ошибку, он выбрасывает BpmnError с префиксом Service и кодом состояния, например, Service404.
3. Использование внутренних транзакций
Контекст
Вы хотите обрабатывать исключения, возникающие после выполнения активности.
Проблема
В случае возникновения исключений вы не хотите завершать активность, а хотите откатить транзакцию к ранее известному состоянию.
Условия
Необходимо валидировать ввод пользователя.
Необходимо валидировать ответ сервиса.
Решение
Движок Flowable выполняет шаги рабочего процесса в чётко определённых транзакциях базы данных. Если на любом шаге возникает исключение, происходит откат всех выполненных действий, и система возвращается в исходное состояние. Поскольку это хорошо протестированное и гарантированное поведение движка, его можно использовать для обработки таких сценариев, как:
Пользователь загружает файл в пользовательской задаче. Необходимо проверить содержимое файла, и если оно некорректно, показать пользователю сообщение об ошибке.
Когда пользователь отправляет данные, мы сохраняем их в базе данных. Если возникает ошибка, мы предлагаем пользователю повторить попытку.
Существует очень хорошая статья в блоге Flowable о транзакциях базы данных и асинхронных флагах. Рекомендуется ознакомиться с ней для более глубокого понимания работы.
См. перевод этой статьи: "Асинхронный флаг без мистики"

После того как пользователь завершает задачу Upload File (Загрузка файла), движок Flowable создаёт новую транзакцию, начиная с конца пользовательской задачи, и выполняет сервисную задачу «Validate Output» (Проверка результата). Код сервисной задачи выглядит следующим образом —
package delegate;
import org.flowable.engine.delegate.BpmnError;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.delegate.JavaDelegate;
import org.flowable.common.engine.api.FlowableIllegalArgumentException;
/**
* @author im-pratham
**/
public class FileContentValidator implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
Validator validator = new ExcelValidator();
boolean result = validator.validate(delegateExecution);
if (!result)
throw new FlowableIllegalArgumentException("File is missing some mandatory field. Please upload file with all mandatory fields.");
}
}
Обратите внимание, что когда метод ExcelValidator#validate возвращает false, мы выбрасываем исключение FlowableIllegalArgumentException, что приводит к откату текущей транзакции. Движок Flowable настроен на отображение сообщения об исключении в форме следующим образом —

Последствия
Этот подход позволяет использовать внутренние транзакции для отката к предыдущим этапам. Однако он не решает проблему побочных эффектов выполненных действий. Например, у вас есть процесс перевода денег, в котором на первом шаге вы списываете сумму, а на следующем — зачисляете её. Если на втором этапе транзакция не удаётся, вы оказываетесь в неконсистентном состоянии, так как деньги не были возвращены на счёт дебитора.
Для обработки побочных эффектов необходимо использовать компенсации.
4. Компенсация (BPMN 2.0)
Транзакции и компенсации в BPMN 2.0 позволяют компенсировать уже выполненные действия при откате.

Контекст
Вы создаёте транзакционный процесс, в котором активности вызывают побочные эффекты.
Проблема
В случае сбоя необходимо компенсировать побочные эффекты неудачных активностей в транзакциях.
Условия
Рабочий процесс является транзакционным.
Шаги рабочего процесса создают побочные эффекты в системе.
Движок рабочего процесса соответствует спецификации BPMN 2.0.
Решение
Компенсация направлена на отмену шагов, которые уже были успешно выполнены, поскольку их результаты и возможные побочные эффекты больше не нужны и должны быть отменены. Если активность (задача или подпроцесс) всё ещё активна, её нельзя компенсировать, её нужно отменить.
Отмена, в свою очередь, может привести к компенсации уже успешно выполненных частей текущей активности, если это подпроцесс. Компенсация выполняется обработчиком компенсации (compensation handler). Обработчик компенсации выполняет необходимые шаги для отмены эффектов активности. В случае подпроцесса обработчик компенсации имеет доступ к данным подпроцесса на момент его завершения (так называемые «снимок данных»).

Компенсация инициируется с помощью события Throw Compensation Event, которое обычно вызывается обработчиком ошибок, в рамках отмены или рекурсивно другим обработчиком компенсации. Это событие указывает активность, для которой должна быть выполнена компенсация, либо явно, либо неявно.

Граничное событие компенсации активируется после завершения прикреплённой активности. Когда возникает событие компенсации, выполняется связанная с ним логика.

В приведённом выше примере, когда происходит событие “Trigger Cancellation” (Инициировать отмену), компенсационные действия — “Undo Book Hotel” (Отменить бронирование отеля) и “Undo Book Flight” (Отменить бронирование рейса) — выполняются столько раз, сколько раз были выполнены их основные активности. Это позволяет реализовать логику компенсации, чтобы отменить работу, выполненную основными действиями.
5. Обработка ошибок из дочернего процесса
Контекст
Ваш рабочий процесс содержит несколько подпроцессов, которые могут завершаться с ошибкой.
Проблема
Подпроцессы иногда завершаются событием Error End Event, и вызывающий процесс должен реагировать на это состояние ошибки.
Условия
Подпроцессы завершаются событием “Error End Event”.
Решение
Всегда полезно явно обозначать все состояния рабочего процесса. Такой подход рекомендуется при создании повторно используемых процессов. Например, если вы создаёте процесс KYC, он может завершиться в состоянии ошибки, если документы некорректны. В этом случае вы хотите передать сообщение вызывающему процессу, чтобы он мог соответствующим образом изменить поток выполнения. Для этого подпроцесс должен завершаться событием “Error end event”.

Для настройки «Error Boundary Event» (граничного события ошибки) в Call Activity необходимо использовать тот же код ошибки, что и в дочернем процессе.

В этой заметке я постарался рассмотреть наиболее распространённые паттерны обработки ошибок с примерами. Надеюсь, вам понравилось. Напишите в комментариях, какой паттерн вам больше всего понравился.

Подписывайтесь на наш телеграм канал
BPM Developers — про бизнес-процессы: новости, гайды, полезная информация и юмор.