Camunda 7 умерла. Пишу свой BPM engine.
TL;DR
Написал легковесный встраиваемый zero-dependency движок бизнес-процессов на Java с двухуровневой транзакционностью. Интересно исследовать потребность бизнеса в таком решении и найти партнеров, готовых обкатать его на деле.
你好
Приветствую IT-сообщество. Давайте знакомиться. Я Фёдор, и я программист. Занимаюсь разработкой с 2008 года, в основном Java. Люблю Camunda, сам её интегрировал раньше в другие проекты.
В ковидный период активно занимался разработкой бизнес-процессов для Camunda в очередной госкомпании. И то, как BPM был интегрирован тут -- сплошная боль. Получив колоссальный негативный опыт, я решил сделать движок, который будет таким же удобным в использовании, как Camunda, но без критических (с моей точки зрения) недостатков.
Любовь с первого взгляда
Изначально мы встретились с Activiti. Тогда шла разработка ESB на базе ServiceMix, и нужен был оркестратор. Activiti казался довольно подходящим решением. Однако разрабатывать процессы было не очень удобно, несмотря на потенциальные возможности движка.
Через пару лет вышла Camunda со standalone платформой, моделером, запуском по тыку, с восхитительной документацией. Теперь разработка процессов стала не только полезной, но ещё и приятной. Эффект был столь впечатляющим, что во многом определил мои вкусы на годы вперед. И до сих пор я считаю, что направление BPM развито слабо и остается перспективным.
Так что же не так с Camunda 7?
Camunda позволяет разработчику делать многое. Слишком многое.
Прямое изменение
Первые сомнения закрались, когда я обнаружил execution.setVariable().
Создавая переменную через mapping, вы делаете процесс понятным и предсказуемым. Когда вы вызываете setVariable из скрипта или из делегата или (упаси, Боже) через reflection -- это превращает разбор инцидентов в кошмар. Если вдруг вы используете Camunda, не делайте так.
Вывод: в переменной, помимо её содержания, важно понимать, кто в неё записал и кто из неё читает.
Кстати о reflection.
Движок никак не защищен от чтения изнутри скрипта или делегата. И если раньше можно было решить эту проблему через песочницу, то теперь, с упразднением Security Manager из Java, песочница может быть запущена только на изолированной JVM.
Благодаря спорным решениям архитекторов, у меня в распоряжении были данные любых процессов, и в том числе oauth токены доступа пользователей (да, некоторые процессы тащили токены из браузера в переменные). Вообще, любой разработчик процессов мог неограниченно распоряжаться рантаймом движка.
Вывод: сервис исполнения скриптов и делегатов должен проектироваться как не связанный с рантаймом движка компонент. Данные для выполнения пользовательского кода должны быть полностью сериализуемы.
Непоследовательность в создании переменных
Когда увидел иерархическую структуру скоупов,сразу решил, что значения в setVariable пишутся в локальный скоуп. Но нет, как ни странно, Camunda пишет переменную в корневой скоуп. А вот output mapping -- в родительский, как и ожидается. Есть тут определенная непоследовательность, однако верю, это тяжелое наследие Activiti.
Горе-камундисты как-то навертели процессы, которые читают из базы сущности, сохраняют "как есть" в корень процесса прямо изнутри делегата, и к концу работы переменные процесса раздуваются до невозможности.
Своими глазами видел переменную currentCompany, из которой во всем процессе читалось только название, но в переменной лежали base64 логотипа, PDF договор и тонна другой бесполезной в данном процессе информации.
Вывод: нельзя давать возможность писать никуда, кроме как в локальный скоуп таски. Все остальные слияния и синхронизации могут быть выполнены через систему маппингов и сигналов. Это автоматически вычищает неактуальные переменные из процесса и мотивирует программистов осмысленно выбирать данные в процесс.
Параллельность
А точнее её отсутствие, стали для меня ещё одним неприятным открытием.
Хотя Camunda и заявляется с возможностью переллельного исполнения, на деле это не так. Из-за глобальных переменных есть большая вероятность получить взаимную блокировку в параллельных ветках, и связанные с этим неприятности.
Поэтому Camunda выполняет процессы в "однопоточном" режиме. Это называется Exclusive, когда Job executor выполняет одно задание в процессе за раз.
Синхронные же участки процесса с распараллеливанием логики и вовсе выполняются в однопоточном процессе (обходом графа процесса вглубь).
Можно сказать, что ничего не мешает поставить async после ветвления -- и это правда, но не всегда возможно. Иногда проектировщики закладывают именно sync логику, например, для гарантированного перехода между UserTask формами в UI, и внутри такого перехода параллельные задачи будут по факту выполнены последовательно.
Я не считаю sync переходы абсолютным злом, но надо понимать, что такое решение серьезно связывает руки и несет риски повторных побочных эффектов (вызовы внешних api), если такие есть.
Вывод: было бы здорово сделать возможность полноценной параллельности.
Зависимости
Хотя ядро Camunda и является во многом изолированным движком, всё же имеет ряд жестких зависимостей, без которых её работа невозможна. Например, логер, gson, spring и ряд других. Однако это может привести к конфликту версий или нежелательным зависимостям.
Вывод: для технологической чистоты лучше исключить из ядра любые зависимости.
Жизненный цикл Camunda 7 завершен в 2025 году.
Тут нечего добавить.
Yape
После того, как я оставил госконтору, обнаружился достаточный резерв ресурсов, чтобы выделить некоторое время на личные проекты. Всё, на что вечно не хватало времени: поковырять микроконтроллеры, написать операционку. Наконец, пройти Готику. Между прочим, решил попробовать BPM движок.
Мотивации от боли в госкомпании хватило, чтобы прототип появился за 3 месяца с небольшим. Включая примитивную админку и сносный моделер. Но архитектура мне не очень нравилась, было сложно в ней разбираться, рефакторинг слабо помогал, к тому же платформа тянула гору библиотек, благодаря которым удалось так быстро добиться результата. В общем, не зашло.
Дальше с переменными успехами и перерывами появилось ещё две инкарнации движка. В конце концов, учитывая все ошибки, я взялся за Yape.
Проект получил модульную структуру (modulith).
Взаимодействие между компонентами устроено по принципу SOA. По сути, весь движок - это большой набор связанных сервисов, что довольно типично для ынтырпрайз. Выделился отдельный слой сервисов, обеспечивающих API Gateway для ядра на уровне Java интерфейсов.
Работа с хранилищем, логером, запуском скриптов (и делегатов) вынесена за абстракции, что позволяет исключить любые внешние зависимости внутри ядра.
Скрипты и делегаты выполняются в изолированном пространстве, например на отдельном сервере.
Транзакции в Yape получились двух уровней. На одном уровне синхронизируются разные execution, в том числе несколько движков в рамках одной базы на основе SSI. Там всё не так просто, и потребовалась ювелирная работа с изоляциями, чтобы получить оптимальную параллельность, и при этом сохранить целостность. Yape - это самое сложное, что я делал в жизни, а транзакционность - это самая сложная часть Yape.
Второй уровень транзакций используется внутри каждого отдельного запуска, и позволяет синхронизировать параллельные виртуальные потоки в одном execution. Оба уровня реализованы в ядре, так что состояние процесса можно сохранять в любом хранилище, будь то СУБД, файлы или POJO.
Output mapping группируется в единую переменную с именем ${node.id}_output. Так что разные таски не могут писать в одну и ту же переменную подпроцесса.
Производительность не фонтан, раза в 2-3 медленнее, чем Camunda, но тут есть куда расти и что оптимизировать.
По итогу
Прямое изменение переменных - невозможно
Reflection в движок - невозможен
Переменные создаются только в локальном скупе
Полноценная параллельность достигается за счет транзакционости и лучшего разграничения сущностей, в частности благодаря изоляции скоупов.
Zero-dependency ядро запустится даже на утюге.
Госзаказ получает велосипед, в смысле честный отечественный движок (ну, после регистрации в реестре).
Yape Spring Platform
Это все здорово, но, как показал опыт Activiti, движок сам себя не запустит. Нужна какая-то платформа для разработчика процессов. Пощупать, что-то проверить, в конце концов, взять за основу продукта.
Я много работал со Spring, поэтому для всех сервисов ядра написал Spring Autoconfig, с JDBC + H2, админку на HTML/JS, моделер BPMN.js.
Теперь процессы можно деплоить, отслеживать на схеме процесса, смотреть на переменные и скоупы, запускать job и userTask. Всё почти как у взрослых.
У меня уже есть демо стенд, но светить им наружу страшно (домашний сервер может быть довольно уязвимым) и хлопотно (организовывать доменное имя) или дорого (арендовать VPS). Будет потребность - будет и доступ.
И что дальше?
Я рассматриваю такие варианты судьбы проекта.
1. Влиться в компанию. Буду проектировать вместе с коллегами, ядро (логику переходов, транзакции) я бы оставил открытым, а решение на нем будет вполне себе коммерческим.
2. Проект выкупают вместе с потрохами. Это будет весьма дорого. Вряд ли.
3. Партнерские проекты. Среднему магазину или интернет-платформе нужен встраиваемый BPMN движок. Работаем по разумным тарифам, 5-10 договоров позволят кормиться и сделать весь проект открытым.
4. Забвение. Ну, тоже вариант. Придется писать резюме с помощью ИИ, чтобы его проверяли через ИИ, на работу прокладкой между IDE и ИИ.
Инвестиции не нужны, потому как MVP уже есть, а рынок толком не изучен.
Если заинтересует, пишите на yapebpm@mail.ru
Да, кстати, абсурдная ситуация с ИИ стала одной из мотиваций написать движок. Мне, блин, нравится создавать. Программистом я стал, когда платили 20-25 тысяч, шел я туда определенно не за деньгами. Не подумайте, я люблю пообщаться с нейронкой, открыть дебаты по сложному вопросу, многие решения в движке были подробно обсуждены с самыми разными представителями железного разума.
Так что помимо прочего этот движок для меня - сохранение возможности заниматься любимым делом.
Чего и вам желаю.