Comunda что это за программа
Camunda external tasks — мощный инструмент для создания приложений с отказоустойчивой и масштабируемой архитектурой
В Тинькофф для разработки систем автоматизации бизнес-процессов мы используем фреймворк Camunda + Spring. Сами бизнес-процессы описываем с помощью BPMN (Business Process Management Notation) в виде блок-схем.
Наиболее часто используемый элемент на наших схемах — service tasks (прямоугольник с шестеренкой). Camunda поддерживает два способа выполнения service tasks:
Это полезно, когда вы собираетесь переиспользовать логику в нескольких приложениях. Или когда вы хотите придерживаться микросервисной архитектуры. Например, отделяя сервисы, которые занимаются бизнес-процессами, от сервисов, которые реализуют технические задачи, такие как генерация отчетов или рассылка.
Вместе с этой возможностью external task дает масштабируемую, отказоустойчивую архитектуру. Чтобы понять, за счет чего это происходит, сначала нужно понять, как external task работает на уровне BPMN и на уровне приложения.
External task in BPMN
External task подразумевает создание задачи, которая может быть выполнена внешним обработчиком. Суть паттерна external task заключается в том, что:
Я хочу, чтобы мое приложение на camunda отвечало только за бизнес-процессы, а email-рассылками занималось любое другое приложение. В таком случае мне отлично подходит паттерн external task. В своем процессе я просто создам задачу на имейл-рассылку и буду ждать, когда ее выполнит какой-нибудь внешний обработчик.
Чтобы на схеме создать external task, необходимо:
Topic — это название очереди, в которую будут складываться задачи одного типа и на которую будет подписываться внешний обработчик.
Теперь, когда в процессе есть external task, можно его запустить, но выполняться он не будет, так как никто его не обрабатывает.
External tasks worker
Паттерн external task хорош тем, что он позволяет реализовывать обработку задач на любом языке, с помощью любых инструментов, которые могут выполнять HTTP-запросы.
Ниже приведен пример из блога camunda. В примере реализован внешний обработчик на javascript, который раз в 20 секунд запрашивает у camunda список задач на обработку. Если есть задачи, то выполняет по ним рассылку и уведомляет camunda о завершении задачи.
Как видно из кода выше, ключевыми методами для обработки external tasks являются fetchAndLock и complete. Первый метод запрашивает список задач и закрепляет их выполнение за собой, а второй информирует об окончании выполнения задачи. Кроме этих двух методов есть и другие, о них вы можете прочитать в официальной документации.
Camunda external task client
Для реализации обработки external tasks camunda предоставила клиенты на Javascript и Java, которые позволяют создавать обработчики внешних задач буквально в несколько строк. Еще есть подробный гайд, в котором описаны основные принципы обработки внешних задач — опять-таки с примерами на Javascript и Java.
Пример реализации внешнего обработчика с помощью ExternalTaskClient:
Если ваша задача требует не просто выполнения какого-то синхронного действия, а запуска целого процесса, то вы вполне можете это сделать, например, запустив процесс через RuntimeService:
В этом примере обработчик external tasks (EmailWorker) при получении задачи запускает процесс SendEmailProcess.
Представим, что этот процесс выполняет какие-то действия, необходимые для отправки рассылки, и в конце вызывает EmailResultDelegate, который, в свою очередь, завершает выполнение external task.
Архитектурные преимущества external task
Стоит отметить, что есть способ запускать процесс в другом приложении camunda более простым способом: POST: /rest/process-definition/key/$
Когда вы используете REST, у вас нет никаких транзакционных гарантий. Но ведь с external task мы тоже работаем посредством REST, в чем тогда разница?
Разница в том, что мы не вызываем внешний сервис напрямую, а лишь публикуем задачи, которые могут быть обработаны. Рассмотрим на примере:
Некоторый внешний обработчик забирает задачу, которая теперь закреплена за ним, но при получении ответа происходит разрыв соединения. Теперь на стороне camunda заблокирована задача, которая не будет обработана, так как внешний обработчик не получил ответ. Но это не страшно: в camunda для external tasks есть тайм-аут, по которому задача снова вернется в очередь, и ее сможет обработать кто-нибудь другой.
Теперь давайте рассмотрим случай, когда внешний обработчик получил задачу, выполнил ее и вызвал метод complete, который завершился ошибкой из-за проблем сети. Теперь вы не сможете понять, была ли задача успешно завершена в camunda или нет. Вы можете попробовать снова, но есть вероятность, что проблемы с сетью будут продолжаться.
В этом случае лучшим решением будет игнорировать проблему. Если задача все-таки была успешно выполнена, то все в порядке. Если нет — по истечении тайм-аута задача снова будет доступна для обработки. Но это означает, что ваш внешний обработчик должен быть идемпотентным или содержать логику для дедупликации задач.
Аналогичная проблема может случиться при запуске нового процесса, поэтому перед этим стоит проверить существующие инстансы с такими же данными, например businessKey.
Помимо высокой отказоустойчивости external task позволяет легко масштабировать внешние обработчики и реализовывать их на любых языках программирования. При этом паттерн помогает реализовывать микросервисы так, чтобы они как можно меньше влияли друг на друга, тем самым повышая их стабильность.
Camunda — что это такое?
Camunda — это BPM-движок для автоматизации бизнес-процессов. Что значат эти слова и какую пользу вы можете получить от использования этого движка читайте в этой статье.
Какое вам дело до Camunda
Camunda может очень сильно, в десятки раз (например если вы устали от «статусов»), снизить затраты на автоматизацию бизнес-процессов и\или оркестрацию микросервисов. Это достигается за счёт:
Пример бизнес-процесса в BPMN
Компоненты системы
Camunda — это набор приложений Modeler, Task List, BPMN Engine, DMN Engine, Cockpit, Admin,Optimize.
Архитектура приложений Camunda
Интерфейс camunda tasklist
Структура BPMN Engine
Слабая связность компонентов между собой позволяет их заменять, разрабатывая свои. Например, я написал свой Cockpit, который намного функциональнее бесплатного.
Как попробовать Camunda самому
Standalone вариант использования
2. Библиотека внутрь java-приложения — это значит, что вы указываете зависимости в своем приложении и работаете с camunda через Java API.
Embedded вариант использования
В этом видео можно посмотреть как сделать своё первое stand-alone приложение:
Весь движок и часть веб-приложений бесплатны, начать использовать их можно прямо сейчас.
В итоге
За счёт бесплатности и классной архитектуры система может решать кучу ваших проблем и прямо сейчас. Если вы планируете её использовать в своей работе и хотите быстро разобраться, как интегрировать этот BPM-движок в вашу инфраструктуру и как лучше построить архитектуру приложений, то мои консультации могут вам помочь.
Вы уже работаете с системой? Как вам? Расскажите в комментариях и поделитесь этой статьей в соц.сетях.
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
Стильная, модная, молодежная разработка BPM на Camunda
BPM-разработка — дело непростое. Это обусловлено тем, что процесс должен быть читаемым и понятным заказчику, а не только корректным с технической точки зрения.
Не все средства разработки бизнес-процессов позволяют найти компромисс между понятным описанием и технической функциональностью. Многие продвинутые средства разработки и описания процессов часто имеют еще один недостаток: они настолько крутые, мощные и сложные, что, пока их делали, технологии сильно шагнули вперед и разработка таким инструментом стала неактуальной.
2018 год принципиально изменил наш подход к разработке бизнес-процессов. Ниже — о том, как эволюционировал этот подход и как менялись мы.
Вместо пролога
Наш отдел занимается разработкой бизнес-процессов — начиная от самых маленьких и незначительных, заканчивая крупными и крайне прибыльными. До последнего времени для разработки мы использовали продукт от IBM, который позволяет достаточно быстро выпустить в продакшен работающий бизнес-процесс.
IBM BPM — мощная платформа, которая включает богатый набор возможностей, таких как описание самих процессов, UI-форм, интеграционных модулей. К тому же эта платформа имеет довольно низкий порог входа, что позволяет начинающим разработчикам практически сразу погружаться в проект. Но есть у этого продукта и недостатки, которые если и не тормозят разработку, то уж точно не способствуют скорости и качеству:
Со всеми этими ограничениями трудно находиться на плаву. Чуть менее года назад поступило предложение попробовать движок Camunda для описания бизнес-процессов. Для начала был выбран не очень большой, но достаточно важный процесс по регистрации терминалов для юрлиц.
Так как его мы переписывали полностью, старого кода почти не было, мы могли практически ни в чем себя не ограничивать и поэтому в качестве языка разработки выбрали Kotlin. Было интересно попробовать этот новый язык, о котором слышали по большей части положительные отзывы. На некоторых других проектах в нашем отделе был успешный опыт внедрения. Финальный стек получился таким: Camunda, Spring Boot 2, Kotlin, Postgre.
Что это такое Camunda?
Camunda — это open-source-платформа для моделирования бизнес-процессов, которая написана на Java и в качестве языка разработки использует Java. Она представляет собой набор библиотек, которые и позволяют выполнять описанные процессы. Для интеграции Camunda в проект достаточно добавить несколько зависимостей. Для хранения процессов можно выбрать in-memory или персистентную СУБД — в зависимости от задач. Мы выбрали Postgre, так как нам важна история для «разбора полетов». По умолчанию платформа разворачивается на H2.
Разработка состоит из двух частей: создание flow-процесса в специальной утилите Camunda Modeler и написание java-кода, который обрабатывает шаги процесса, описанные на диаграмме. Чтобы из процесса вызвать java-код, достаточно реализовать интерфейс JavaDelegate, поднять этот Bean (можно указать делагат по полному имени, но через Bean удобнее и гибче) в контексте и указать его id на нужном шаге процесса. На Kotlin делегат выглядит еще более лаконично. Логика работы делегатов довольно проста: что-то вычитали из контекста, выполнили какие-то действия и положили обратно в контекст.
Рабочее окно Camunda Modeler
Пример делегата на Java:
Пример делегата на Kotlin:
В делегате можно описывать бизнес-логику, интеграции и всё, что душе угодно.
Мы стараемся создавать прослойку в виде бизнесового компонента с логикой, а сам делегат использовать только как связующее звено с flow процесса, чтобы как можно меньше смешивать код и процесс.
В большинстве случаев этот подход удобен и работает успешно. Взаимодействие с процессом происходит через DelegateExecution, который позволяет, например, работать с контекстом, инцидентами и так далее.
Это то, чего мы хотели?
В самом начале, при выборе инструмента, мы искали решение со следующими возможностями:
Хорошо читаемый трейс, возможность задания числа попыток выполнить шаг перед падением, кастомный обработчик при падении — например, если при падении мы хотим поменять статус некоторой сущности на Error. Последнее сделать достаточно легко, просто реализовав DefaultIncidentHandler. Правда, есть забавный момент при работе этого обработчика: код восстановления из ошибки срабатывает при каждом заходе на шаг процесса. Не могу сказать, что это — супербаг или проблема. Скорее, об этом нужно просто помнить и учитывать при разработке.
Мы решили это примерно так:
GUI у Camunda есть, и он неплохой.
Но если хочется чуть больше, например миграцию инстансов между версиями процессов, то уже придется потрудиться. В дефолтном UI только минимальный функционал, зато есть очень мощный Rest API, который позволяет создать свою админку — крутую и навороченную.
Именно по пути своей админки мы пошли. Наш архитектор бизнес-процессов в довольно короткий срок ее запилил, включив туда функции просмотра истории по уже завершенным процессам, миграции между версиями и так далее.
Rest у Camunda действительно мощный и позволяет творить с процессами практически что угодно. Например, стартовать процесс можно по /process-definition/key/aProcessDefinitionKey/start таким нехитрым запросом:
Пример взят из официальной документации, которая содержит обширное описание различных кейсов использования этого API.
Для юнит-тестирования мы используем обычный Junit. Плюс есть довольно интересная библиотека для тестирования самого процесса — ‘org.camunda.bpm.extension’, name: ‘camunda-bpm-assert’. С ее помощью можно описывать тесты для проверки flow-процесса.
Это довольно удобно, так как искать проблемы в случае багов во flow зачастую сложнее, чем в коде. Такое тестирование защищает, например, от неаккуратного рефакторинга и несколько раз действительно нас выручало.
Необходимость в Java 8 частично отпала, так как использование Kotlin на многих процессах устранило потребность в «восьмерке». Kotlin очень хорошо вписался в проект и позволил сосредоточиться только на написании бизнес-логики. Трудно поверить, но в основном всё то, что говорят о крутости Kotlin, — правда. Сущности с большим числом полей, которыми славятся практически все приложения с интеграциями, теперь выглядят не так страшно, и маппинги между сущностями стали гораздо более читабельными. Часто критикуемое null safety действительно работает и выручает в большинстве случаев.
Community у Camunda достаточно развитое. Об этом говорит тот факт, что постоянно появляются новые библиотеки на GitHub для тестирования и метрик.
Приятно, что Camunda отлично интегрируется со Spring. Добавить необходимых зависимостей, пару аннотаций и пару конфигурационных бинов — собственно, вот и вся интеграция! В результате мы пишем обычное spring-приложение, к которому все привыкли, добавляя flow бизнес-процесса. Взаимодействие происходит через Java API, которое позволяет выполнять манипуляции с процессами из java-кода.
Например, запустить процесс можно всего одной командой:
Здесь MyTestProcess — Id-шник процесса, а не инстанса. MyBusinessKey — уникальный ключ для запускаемого инстанса процесса. Мы обычно используем для этого поля некое бизнесовое значение — для более быстрой навигации между инстансами и поиском.
Примерно таким же образом можно разбудить «заснувший» процесс.
Заметных минусов или каких-то проблем, с которыми мы столкнулись, особо вспомнить не получается. В итоге за довольно короткий промежуток времени получилось сделать вполне рабочий процесс и благополучно вывести его в продакшен. На платформе внедряются другие процессы и вполне успешно. Сейчас на Camunda у нас запущено около 15 приложений, на которых единовременно крутится около 100 тысяч процессов.
Вместо эпилога
Вот несколько ресурсов, которые были полезны при внедрении описанного выше стека. Рекомендую ознакомиться с ними, если интересна дополнительная информация по теме.
Управление распределенными транзакциями с помощью Camunda
Мы в компании Леруа Мерлен активно используем микросервисную архитектуру для построения нашего IT-ландшафта. Для начала я бы хотел рассказать какую проблему мы решаем с помощью микросервисов. Для этого рассмотрим пример.
Мы привозим большое количество разнообразных товаров. Одни и те же товары могут поставляться различными поставщиками, упаковываться в различных коробках и по-разному укладываться на паллетах. Данные о размерах коробок, их весе, хрупкости, а также о том как они уложены на паллетах называются логистическими данными. Логистические данные необходимы во многих бизнес-процессах компании, например:
при расчете места под хранение на складе;
при планировании транспорта для доставки от поставщика
при планировании доставки клиенту;
при расчете себестоимости товара;
Зачастую за различные бизнес-процессы отвечают разные системы, разрабатываемые разными командами. Подобная ситуация применима к очень большому числу сервисов, которыми мы оперируем внутри компании. Поэтому мы декомпозируем наши системы на микросервисы с четко очерченными бизнес-функциями и каждый микросервис предоставляет API для взаимодействия (получение данных и изменение данных).
Микросервисы хранят свои данные в БД и у одной БД может быть только один микросервис-владелец, ответственный за эти данные. Только он может читать данные из БД и изменять их.
Таким образом, данные становятся пригодными для повторного использования в рамках множества систем/бизнес-процессов, а микросервисы можно разрабатывать и масштабировать независимо.
Распределенные транзакции
Итого, мы имеем: множество микросервисов со своими базами данных, множество потребителей, которым необходимы эти данные и необходимость иметь возможность консистентно изменять данные в разных БД. Поскольку у каждого сервиса своя база данных и только сам сервис может что-либо изменять в ней, необходим унифицированный подход, с помощью которого сервисы будут коммуницировать между собой.
Существует два основных подхода организации взаимодействия микросервисов: оркестрация и хореография.
Гарантии согласованности данных
Перед тем, как мы более детально приступим к рассмотрению паттерна саги, давайте поговорим что нам могут дать микросервисы в плане гарантий согласованности данных. Поскольку данные, которые нам необходимо изменять, распределены по нескольким базам данных, гарантии ACID в их первоначальной формулировке для нас неприменимы.
Atomicity теперь подразумевает атомарность в рамках одного шага саги, поскольку один шаг саги изменяет данные только в одном микросервисе и изменяет их атомарно.
Consistency теперь для нас значит eventually consistency, т.е. в отдельный момент времени данные могут быть неконсистентны, но через какое-то время данные должны прийти в целостное состояние
Isolation исчезает, поскольку пока мы правили одну сущность в рамках шага саги, кто-то уже мог поправить вторую сущность и мы вынуждены будем откатывать наши изменения самостоятельно
Хореография
В случае хореографии нет единой точки, которая содержала бы в себе информацию о том, какую последовательность действий включает в себя сага. Вместо этого каждый микросервис содержит в себе только информацию о своей части саги.
Поскольку цель этой статьи рассказать об использовании оркестраторов, то далее я сосредоточусь на паттерне оркестрации.
Оркестрация
В случае с оркестрацией вся бизнес-логика выносится в сервис-оркестратор. Оркестратор гарантирует, что в результате выполнения операции по изменению данных в разных микросервисах, данные останутся консистентны.
В случае работы через оркестратор, мы получаем преимущество: саги не размазаны по множеству микросервисов, а сосредоточены в оркестраторе. Оркестратор может изменяться и деплоиться независимо от источников данных, что уменьшает его влияние на доступность других сервисов, а также позволяет ему независимо масштабироваться.
Camunda и bpmn-нотация
На рисунке 4 справа показана одна такая очень простая сага, которая отвечает за выполнение двух задач:
создание транспортной заявки
запуск бизнес-процесса для созданной заявки;
Передача данных между обработчиками
Теперь рассмотрим подробнее взаимодействие обработчиков serviceTask-ов. Поскольку serviceTask-и могут быть зависимы между собой, им необходимо обмениваться данными. Сервисный таск при получении задачи на исполнение, получает также и набор переменных variables. Он может читать данные из variables и по завершении писать данные в variables и отправлять обратно в Camunda. Эти данные будут передаваться дальше по процессу другим сервисным таскам. Т.е. они будут существовать на протяжении всей жизни процесса.
Запуск Camunda-процесса из Java-кода
Рассмотрим как будет исполняться сага если бы мы разрабатывали оркестратор на Java встраиваемый в Camunda. Мы должны были бы создать SpringBoot-приложение, в которое интегрировалась бы Camunda, написать два Java-класса-делегата, указать их названия в интерфейсе Camunda-modeler-а в качестве делегатов serviceTask-ов, создать роут, который будет принимать пользовательский запрос в SpringBoot-приложении и запускать в нем процесс Camunda. Как это будет выполняться показано на рисунке. После запуска процесса, Camunda запустит первый serviceTask, он отработает, затем второй, после успешной работы управление вернется обратно в Java-код.
Такой подход к разработке оркестратора в виде Standalone-приложения возможен только на Java (JVM-языках). Для остальных же языков необходимо поднимать Camunda как отдельное приложение и использовать ServiceTask-и с типом external, о которых поговорим ниже. В принципе, ничего не мешает таким же образом работать и на Java-приложениях, чтобы масштабировать по отдельности Camunda и Java-приложение с обработчиками тасков.
Запуск Camunda-процесса через HTTP
Мы в компании используем для разработки бэкендов кроме Java еще и nodejs. Поэтому далее я буду описывать подход в терминах nodejs, но, в принципе, все перечисленное характерно и для других языков программирования.
Как мы видим, тут есть очень существенное отличие от работы из Java-кода. Если в случае с Java-кодом мы запускаем процесс и его исполнение происходит синхронно, то в случае с nodejs, исполнение происходит асинхронно. Мы отправляем POST-запрос, он запускает процесс, видит, что первый serviceTask является external, кладет сообщение в топик и тут же отвечает нам в Response, что процесс запущен.
Но нам же надо как-то узнать результат выполнения процесса, что он завершился, успешно ли он завершился, если завершился с ошибкой, то с какой.
К сожалению, подписки на событие завершения процесса в Camunda нет и это большая боль.
Запуск Camunda-процесса через HTTP с ожиданием завершения
Самое простое решение этой проблемы в лоб выглядит так: мы пытаемся опрашивать Camunda на предмет статуса процесса. При запуске процесса мы получаем уникальный идентификатор процесса (но также можем и задавать его самостоятельно в виде businessKey). Как только статус процесса изменится на complete, это будет значить что процесс отработал.
Это решение рабочее, но создает излишнюю нагрузку на Camunda, поскольку запуск каждого процесса плодит множество запросов на проверку статуса, а мы запускаем параллельно множество процессов. Хотелось бы иметь более оптимальное решение, поэтому рассмотрим альтернативный вариант.
Запуск Camunda-процесса через HTTP с ожиданием завершения v2
Будем использовать redis в качестве хранителя статуса и механизма оповещения. Перед тем, как запустить процесс в Camunda, оркестратор создает запись в redis. Сам процесс же в Camunda модифицируется таким образом, что в самом конце добавляется еще один обработчик sendMessage, который должен оповестить redis о завершении исполнения процесса. Оркестратор же может подписаться на изменение записи и при проставлении флага завершения из обработчика, он будет узнавать об этом и сможет ответить клиенту информацией о результате исполнения процесса-саги.
Давайте рассмотрим всю последовательность действий, которая происходит на уровне оркестратора при исполнении операции создания транспортной заявки.
оркестратор принимает post запрос с параметрами заявки;
далее он генерирует uid и создает запись с таким uid в качестве ключа в redis и подписывается у redis-а на изменение ключа uid;
затем оркестратор запускает на исполнение процесс в Camunda, указывая в качестве ключа сгенерированный uid;
обработчик external serviceTask-а «Create Transport Order» получает задание на исполнение, выполняет запрос в object-сервис и создает сущность транспортной заявки;
после этого обработчик external serviceTask-а «Start transport order process» получает задание на исполнение и запускает процесс по согласованию транспортной заявки;
обработчик sendMessage получает сообщение с businessKey, отправляется в redis и помечает соответствующее ключу businessKey значение на completed;
оркестратор получает от redis-а уведомление о том, что значение изменилось и отвечает клиенту статусом 201;
Обработчики исключений
Поскольку изменение данных производится не из одного места, а из множества микросервисов и в каждом из них возможны ошибки, исключительные ситуации и частичные отказы, мы должны уметь такие ситуации корректно обрабатывать. Для этого можно использовать механизм исключений. Давайте посмотрим какие инструменты предоставляет Camunda для работы с исключениями.
Когда обработчик serviceTask-и получает запрос на обработку, он может его успешно обработать, но может и отреагировать ошибкой.
Camunda предоставляет два основных типа исключений:
Поговорим о каждом из них подробнее.
Рассмотрим очень простой процесс, для которого не определены обработчики исключений.
Допустим на первом же serviceTask-е возникает ошибка.
— Если мы используем HandleBpmnError, процесс сразу же завершится, в хранилище incidents упадет сообщение об ошибке. Также мы можем при генерации handleBpmnError положить ошибку в variables.
— Если же мы используем HandleFailure, то процесс не завершится, а его выполнение приостановится и он зависнет на текущей таске, ожидая ручного вмешательства. Также при вызове handleFailure, мы можем передать количество попыток и timeout между попытками. Тогда сервисный таск будет вызываться повторно до тех пор, пока не выполнится успешно или же пока не исчерпается количество попыток. После этого он также остановится и будет ждать ручного вмешательства.
в случае handleFailure все отработает точно также как и раньше, перехода по новой ветке не возникнет
а вот в случае с handleBpmnError произойдет переход по альтернативной ветке (на которую указывает обработчик исключений. При этом, в следующем сервисном таске будут доступны variables и инциденты и в случае необходимости можно получить информацию о них.
Есть и другие обработчики исключений, но для externalTask-ов они не работают, поэтому в этой статье они не рассматриваются.
Компенсация выполненной операции
Поскольку наши данные разбросаны по разным сервисам, мы вынуждены сначала изменять данные в одном сервисе, затем в другом. При этом, может возникнуть ситуация, когда мы сначала изменили данные в первом сервисе, потом попытались изменить во втором, но это оказалось невозможно. В таком случае, мы должны откатить изменения, которые мы произвели в первом сервисе.
Для этого мы можем воспользоваться механизмом BPMN-исключений, которые нам предоставляет Camunda и пустить ход выполнения процесса по альтернативной ветке, как показано на слайде.
Давайте посмотрим как будет выглядеть такая сага.
Пользователь в интерфейсе открыл заявку, нашел подходящий для ее исполнения транспорт, нажал кнопку применить и в оркестратор пришли идентификатор транспортной заявки и идентификатор транспорта.
Полная последовательность шагов при этом выглядит следующим образом:
контроллер в оркестраторе принимает post запрос с параметрами transportId и transportRequestId;
далее он генерирует uid и создает запись pending с uid в качестве ключа в redis и подписывается в redis-е на изменение ключа uid;
затем запускает на исполнение процесс в Camunda, указывая в качестве businessKey сгенерированный uid;
обработчик external serviceTask-а «Book Transport» получает задание на исполнение, выполняет запрос в object-сервис с транспортом и бронирует транспорт на указанное время;
обработчик serviceTask-а «close Transport Request» получает задание на исполнение, выполняет запрос в микросервис с транспортными заявками и видит, что заявка уже закрыта. В этом случае он кладет в variables переменную с именем error и генерирует исключение;
обработчик исключений на уровне процессов Camunda ловит это исключение и отправляет исполнение по альтернативной ветке;
вызывается serviceTask «Book Transport Revert» и он удаляет бронь транспорта для данной transportRequestId;
и, наконец, вызывается обработчик sendMessage, он проверяет наличие переменной error, видит, что она есть и отправляет в redis значение по ключу businessKey ошибку в виде json-объекта;
контроллер, который подписался на изменения в redis для данного ключа получает ошибку и отправляет её клиенту;
Если присмотреться внимательно к этому слайду, мы можем увидеть здесь проблему. Сначала транспорт перешел в статус «Забронирован», а потом вышел из этого статуса. А что делать если мы должны как-то реагировать на такие сообщения и отсылать их в другую систему? Что тогда? Отправлять туда сообщения об отмене? Или же допустим, в подобной схеме после первого сервисного таска, кто-то изменил сущность дальше по процессу, что делать? Как теперь в Book Transport Revert не откатить новые изменения?
Давайте немного модифицируем нашу схему, чтобы избежать подобных проблем.
Компенсация выполненной операции v2
И также важно помнить: как только в системе появляются lock-и, вместе с ними появляются и deadlock-и. Важно уметь мониторить заявки, зависшие в статусе reserved на длительное время и эскалировать инциденты об этом.
Retry-политика на случай частичных отказов
И последний пример. При создании транспортной заявки мы должны одновременно запустить бизнес-процесс по этой заявке.
Поскольку эти две операции выполняются в разных сервисах, это тоже будет распределенной транзакцией.
на первом этапе в сервисе с транспортными заявками мы создаем сущность заявки и кладем идентификтор созданной заявки в variables;
на втором мы запускаем процесс в Camunda и в качестве businessKey указываем идентификатор созданной заявки;
Однако, здесь отсутствует компенсирующее действие. Дело в том, что нет никакой объективной причины почему процесс может быть не запущен. Внутри обработчика external serviceTask-а мы также выполняем POST-запрос в Camunda для запуска процесса. Если нам в ответ пришел не 200-ый статус, а 503-ий, мы можем сгенерировать не исключения выхода, а исключение, которое запустит serviceTask через определенный интервал времени повторно с помощью handleFailure. Таким образом, мы можем записать сообщение в лог и запланировать повторное выполнение задачи.
Далее возможны несколько сценариев:
после нескольких повторных запусков ей все же удастся выполниться (например, восстановится сеть или поднимется сервис);
дежурный посмотрит alert, вмешается и устранит причину проблемы;
исчерпается количество попыток повторного запуска и процесс станет на паузу, ожидая ручного вмешательства дежурного;
Хочу заметить, что в этом поведении нет ничего незаконного и оно, в принципе, ничем не отличается от подхода с хореографией. Там тоже вполне возможна ситуация, когда сервис, который должен был прочитать сообщение оказался недоступен и в течение какого-то времени сообщение не будет обработано. А когда сервис поднимется, он подхватит сообщение и обработка завершится.
И также следует заметить, что исполнение сервисного таска по запуску бизнес-процесса можно выполнять асинхронно. В таком случае процесс не будет ждать окончания работы сервисного таска и пойдет дальше.
Zeebe как альтенратива Camunda
Статья получилась довольно длинной, а за рамками данной статьи остались производительность и масштабирование. Это не менее обширная тема и о ней поговорим в следующей статье.