[Программирование, Kotlin] Дорога к BPMN

Автор Сообщение
news_bot ®

Стаж: 6 лет 3 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
29-Дек-2020 13:32

Привет! Меня зовут Лев, и я инженер в новосибирской команде интеграционных сервисов ДомКлик. Мы разрабатываем (микро)сервисы, которые связывают между собой множество разрозненных систем, а также делают многие процессы быстрыми и прозрачными для конечного пользователя. Мы используем ставший уже стандартным стек: Kotlin, Spring Boot, Hibernate, Liquibase и т. д. И нам для наших сервисов (на тот момент пока ещё одного) потребовался механизм исполнения бизнес-процесса. Требования к нему были следующие:
  • каждое действие как отдельный независимый модуль;
  • stateless-движок -> stateful-задача;
  • перезапуск некорректно отработавших задач заранее заданное количество раз;
  • возможность горизонтального масштабирования;
  • механизм должен быть асинхронным;
  • разработать его нужно максимально быстро и просто.
Custom Business Process EngineМы немного подсмотрели структуру сервиса у наших коллег, что-то добавили от себя, и получился у нас простейший движок следующего вида. Четыре базовых SpringService: JobProcessor, JobRunner, JobService и LaunchService, а также базовая сущность задачи — JobEntity. Разберём их подробнее. Самое первое и главное — сущность задачи (Job), вокруг которой построен весь механизм. Она имеет следующие поля:
  • id — идентификатор задачи;
  • status — статус исполнения задачи (PENDING, READY, INPROGRESS, COMPLETED, ERROR);
  • runCount — текущее количество попыток исполнения текущей задачи;
  • delayedTime — время, спустя которое можно повторить исполнение задачи (возрастает для каждой следующей попытки);
  • archived — признак нахождения задачи в архиве;
  • request — тут хранится сериализованный (JSON) запрос на исполнение задачи.
Поскольку наш сервис должен был быть асинхронным, да ещё и разрабатывался максимально срочно, мы использовали PostgreSQL в качестве персистентной очереди запросов. Запрос порождает в базе задачу.Исполнение вышеописанной задачи отдаётся следующим спринг-сервисам:
  • JobProcessor имеет лишь один метод process(jobs: List<Long>), принимающий на вход список идентификаторов задач и запускающий их на исполнение методом run сервиса jobRunner.
  • JobRunner тоже имеет лишь один метод run, вызываемый из JobProcessor. Он получает из jobService список задач по идентификаторам, проверяет, не превышено ли максимальное количество попыток вызова, и запускает задачу в launchService.
  • LaunchService имеет один метод launch. Именно в нём и выполняются все бизнес-операции. При успешном исполнении статус задачи переводится в COMPLETED, иначе, в зависимости от значения runCount, возвращается в READY или завершается со статусом ERROR.
  • JobService — основной сервис для работы с сущностью задачи. Он может класть в БД новую задачу, выбирать из БД задачу по идентификатору и по статусу, менять статус.
Custom Business Process Engine (+ Phases)Поскольку количество действий в бизнес-задачах начало расти, а при падении по каким-либо причинам сервис заново повторял весь процесс для задачи (особенно если на каком-то этапе было взаимодействие со stateful-сервисом), мы решили разделять бизнес-процесс на логические модули, назвав их фазами (Phases). Таким образом, сущность задачи получила новое поле phase и метод next(), возвращающий нам следующую фазу исполнения. Кроме того, JobService стал немного умнее и выбирает выполняемую над задачей операцию в зависимости от её текущей фазы. Теперь мы могли перезапускать процесс в случае ошибки не с самого начала, а лишь с фазы, на которой произошла ошибка. К тому же при горизонтальном масштабировании разные фазы задачи могут выполняться разными экземплярами сервиса, поэтому в случае падения одного или нескольких экземпляров сервиса задача будет подхвачена оставшимися.Теперь процесс исполнения запроса выглядел так. Контроллер сервиса вызывается по REST API и формирует в базе данных в таблице с задачами новую запись. Исполнением задач занимается SpringService JobRunner:
  • Берёт задачу в статусе READY.
  • Переводит её в статус IN_PROGRESS и записывает.
  • Смотрит на фазу, и в зависимости от её значения выполняет какое-то действие. Для передачи данных и сохранения результата используется поле context, сериализованное в JSON.
  • Если финальная фаза, то сервис переводит задачу в статус COMPLETE, сохраняет в базу и отправляет сообщение об успехе сервису-инициатору. Если фаза не финальная, то сервис переводит задачу в следующую фазу со статусом READY.
В случае ошибки:
  • Если значение retries достигло maxRetries, то отвечаем сервису-инициатору ошибкой и возврашаем в базу задачу со статусом ERROR.
  • Если значение retries меньше maxRetries, то делаем retries++ и возвращаем в базу со статусом READY.
Custom Business Process Engine (+ Phases) (+ Activiti)Наши сервисы разрастались, переходы между фазами переставали быть линейными, сценарии работы усложнялись, а код launchService и JobEntity.next() начал становиться нечитаемым и трудноподдерживаемым. С этим надо было что-то делать, и мы сделали! Ранее у нас был опыт работы с Activity, так что мы решили использовать этот BPMN-движок вместо последовательности фаз.
BPMN (Business Process Model and Notation) — нотация для описания бизнес-процессов, позволяющая представлять их визуально.
В интернете существует множество инструкций, как начать работать с Activity, так что здесь я это описывать не буду. Расскажу лишь про опыт использования. Главным достоинством стала возможность визуально представить процесс выполнения нашего бизнес-конвейера. Логика формирования данных в зависимости от условий теперь была более ясная, и это сократило количество ошибок при разработке. Например, нам требуется формировать разные пакеты документов для разных типов клиентов по сложному набору критериев. И когда таких критериев и типов клиентов становится слишком много, такие ветвления в коде уже перестают восприниматься визуально, а ошибки плодятся в разы быстрее. И тут нас выручает наглядная графическая схема процесса. Теперь LaunchService лишь выбирал и запускал нужный нам сценарий, а вся логика переходов была на картинке.Это было лишь переходным этапом, и приведя сервис к такой конструкции, мы начали понемногу выпиливать наш кастомный движок. Оставили все бизнес-процессы на Activity, чьи сценарии запускались уже из контроллера. В дальнейшем их обработку полностью отдали Activity.Однако это решение привнесло с собой и некоторые недостатки. Главными из них стали:
  • Сложность отладки кода блоков. В Activity используется groovy-script, а сами схемы описываются огромными XML со встроенным groovy-script. Так мы получили трудноотлаживаемую часть проекта, разбросанную по *.bpmn xml-файлам.
  • Наличие в проекте дополнительного языка (Groovy) усложнило поддержку проекта.
  • Нет никакой валидации groovy-script в задаче Activity, а значит можно легко опечататься в названии метода или сигнатуре. При сборке не получится проверить валидность лишь модульными тестами.
  • Проект не выглядит живым, на заведенную нами уже более года назад багу (https://github.com/Activiti/Activiti/issues/2911) ответ не получен до сих пор.
  • Конкуренты (Camunda, Flowable) предоставляют более удобную и широкую функциональность и поддержку (а вот об этом в другой раз).

===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_kotlin, #_bpmn, #_bpmn_2.0, #_kotlin, #_blog_kompanii_domklik (
Блог компании ДомКлик
)
, #_programmirovanie (
Программирование
)
, #_kotlin
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 19-Май 14:39
Часовой пояс: UTC + 5