BPM-разработка — дело непростое. Это обусловлено тем, что процесс должен быть читаемым и понятным заказчику, а не только корректным с технической точки зрения.
Не все средства разработки бизнес-процессов позволяют найти компромисс между понятным описанием и технической функциональностью. Многие продвинутые средства разработки и описания процессов часто имеют еще один недостаток: они настолько крутые, мощные и сложные, что, пока их делали, технологии сильно шагнули вперед и разработка таким инструментом стала неактуальной.
2018 год принципиально изменил наш подход к разработке бизнес-процессов. Ниже — о том, как эволюционировал этот подход и как менялись мы.
Вместо пролога
Наш отдел занимается разработкой бизнес-процессов — начиная от самых маленьких и незначительных, заканчивая крупными и крайне прибыльными. До последнего времени для разработки мы использовали продукт от IBM, который позволяет достаточно быстро выпустить в продакшен работающий бизнес-процесс.
IBM BPM — мощная платформа, которая включает богатый набор возможностей, таких как описание самих процессов, UI-форм, интеграционных модулей. К тому же эта платформа имеет довольно низкий порог входа, что позволяет начинающим разработчикам практически сразу погружаться в проект. Но есть у этого продукта и недостатки, которые если и не тормозят разработку, то уж точно не способствуют скорости и качеству:
- Нет вменяемого контроля версий. IBM BPM просто не дает возможности нормально хранить процессы (код) в репозитории и использует свое собственное хранилище, которое не знает о таком понятии, как merge, например.
- Разработка на Java 6. Возможно, на момент написания этой статьи уже можно разрабатывать на Java 7, но в 2019 году это слабое утешение.
- IBM BPM крутится на WebSphere, в итоге разработчикам надо запасаться терпением при каждом обновлении своего модуля. К тому же это дополнительная головная боль для админов, которым периодически приходится возвращать к жизни этого монстра в случае его зависания.
- Разработка интеграционных модулей в среде Integration Designer, которая в действительности является затюненным не в лучшую сторону Eclipse.
- Нет нормальной возможности Unit-тестирования.
- Высокая стоимость платформы.
Эти недостатки кроме чисто технического неудобства разработки породили еще одну проблему, которая, возможно, гораздо серьезнее всех вышеперечисленных. Во времена Java 12, Kotlin, микросервисов и прочих модных трендов и штук все эти нюансы очень демотивируют команду. Трудно испытывать радость при разработке в постоянно виснущем Integration Designer на Java 6 в 2019 году.
Со всеми этими ограничениями трудно находиться на плаву. Чуть менее года назад поступило предложение попробовать движок 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:
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;
public class JavaExampleDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
String someVar = (String) execution.getVariable("someVariable");
// some actions
execution.setVariable("otherVariable", "otherValue");
}
}
Пример делегата на Kotlin:
import org.camunda.bpm.engine.delegate.DelegateExecution
import org.camunda.bpm.engine.delegate.JavaDelegate
class KotlinExampleDelegate: JavaDelegate {
override fun execute(execution: DelegateExecution) {
val someVar = execution.getVariable("someVariable")
//some code
execution.setVariable("otherVariable", "someValue")
}
}
В делегате можно описывать бизнес-логику, интеграции и всё, что душе угодно.
Мы стараемся создавать прослойку в виде бизнесового компонента с логикой, а сам делегат использовать только как связующее звено с flow процесса, чтобы как можно меньше смешивать код и процесс.
В большинстве случаев этот подход удобен и работает успешно. Взаимодействие с процессом происходит через DelegateExecution, который позволяет, например, работать с контекстом, инцидентами и так далее.
Это то, чего мы хотели?
В самом начале, при выборе инструмента, мы искали решение со следующими возможностями:
- Восстановление процесса ровно с того места, где произошел сбой, и желательно, чтобы это было из коробки.
- Некоторый GUI, где можно посмотреть, что вообще с процессом происходит.
- Возможность написания юнит-тестов не только на логику и интеграцию, но и на сам процесс.
- Java 8 и выше.
- Развитое сообщество.
С возможностью восстановления и анализа ошибок у Camunda все отлично.
Хорошо читаемый трейс, возможность задания числа попыток выполнить шаг перед падением, кастомный обработчик при падении — например, если при падении мы хотим поменять статус некоторой сущности на Error. Последнее сделать достаточно легко, просто реализовав DefaultIncidentHandler. Правда, есть забавный момент при работе этого обработчика: код восстановления из ошибки срабатывает при каждом заходе на шаг процесса. Не могу сказать, что это — супербаг или проблема. Скорее, об этом нужно просто помнить и учитывать при разработке.
Мы решили это примерно так:
override fun resolveIncident(context: IncidentContext) {
val incidentList = Context.getCommandContext().incidentManager.findIncidentByConfiguration(context.configuration)
if (incidentList.isNotEmpty()) {
// некоторый код при восстановлении инцидента
}
}
GUI у Camunda есть, и он неплохой.
Но если хочется чуть больше, например миграцию инстансов между версиями процессов, то уже придется потрудиться. В дефолтном UI только минимальный функционал, зато есть очень мощный Rest API, который позволяет создать свою админку — крутую и навороченную.
Именно по пути своей админки мы пошли. Наш архитектор бизнес-процессов в довольно короткий срок ее запилил, включив туда функции просмотра истории по уже завершенным процессам, миграции между версиями и так далее.
Rest у Camunda действительно мощный и позволяет творить с процессами практически что угодно. Например, стартовать процесс можно по /process-definition/key/aProcessDefinitionKey/start таким нехитрым запросом:
{
"variables": {
"aVariable" : {
"value" : "aStringValue",
"type": "String"
},
"anotherVariable" : {
"value" : true,
"type": "Boolean"
}
},
"businessKey" : "myBusinessKey"
}
Пример взят из официальной документации, которая содержит обширное описание различных кейсов использования этого 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-кода.
Например, запустить процесс можно всего одной командой:
runtimeService.startProcessInstanceByKey(
"MyTestProcess",
"MyBusinessKey",
mapOf(
"key1" to "value1",
"key2" to "value2",
)
)
Здесь MyTestProcess — Id-шник процесса, а не инстанса. MyBusinessKey — уникальный ключ для запускаемого инстанса процесса. Мы обычно используем для этого поля некое бизнесовое значение — для более быстрой навигации между инстансами и поиском.
Примерно таким же образом можно разбудить «заснувший» процесс.
Заметных минусов или каких-то проблем, с которыми мы столкнулись, особо вспомнить не получается. В итоге за довольно короткий промежуток времени получилось сделать вполне рабочий процесс и благополучно вывести его в продакшен. На платформе внедряются другие процессы и вполне успешно. Сейчас на Camunda у нас запущено около 15 приложений, на которых единовременно крутится около 100 тысяч процессов.
Вместо эпилога
Вот несколько ресурсов, которые были полезны при внедрении описанного выше стека. Рекомендую ознакомиться с ними, если интересна дополнительная информация по теме.
- Camunda хорошо документирована — большинство нужных кейсов мы нашли в официальной документации.
- Более детально про платформу, интересные кейсы и вообще про bpm можно посмотреть на этом канале. Его ведет наш коллега, который и предложил использовать Camunda.
- Хороший доклад от Павла Финкельштейна про внедрение Kotlin.
Автор: Николай Шипяков