Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Всем привет! Ранее мы упоминали, что платформа Unidata активно работает с бизнес-процессами и поддерживает нотацию BPMN в основе их проектирования. Для разработки БП мы используем open source движок Activiti BPMN, базирующийся на java. Среди доступных продуктов с открытым исходным кодом для проектирования бизнес-процессов мы выбрали Activiti 2-м причинам:
Мы уже имели опыт работы с этим продуктом
Имеется удобная для коммерческой разработки лицензия Apache
В этой статье мы расскажем о собственном опыте применения Activiti при разработке реальных бизнес-процессов заказчика.
Первым шагом в разработке бизнес-процесса является формирование схемы бизнес-процесса. Например, плагин Activiti для Eclipse позволяет визуально проектировать бизнес-процесс в нотации BPMN 2.0. В визуальном редакторе есть для этого все необходимые компоненты: таски различных типов, подпроцессы, шлюзы, триггеры, скрипты и другие. Элемент BPM достаточно перетянуть из списка на схему, а далее связать все элементы «стрелочками», так в нотации BPMN обозначается «поток операции» (Sequence Flow). Схема бизнес-процесса средствами Activiti записывается в специальной конфигурационный xml-файл, который в дальнейшем можно редактировать. Ниже представлен пример несложного бизнес-процесса.
![](https://habrastorage.org/getpro/habr/upload_files/fee/1ef/0f1/fee1ef0f10d514f3ebd1c661f3899eac.png)
Кратко информация о том, для чего нужны элементы BPMN, представленные на схеме:
UserTask – основная задача, которую выполняет пользователь. Шаг бизнес-процесса
ServiceTask используется когда при переходе от одной задачи к другому необходимо выполнить автоматическое действие, например рассчитать время жизни задачи.
MailTask используется для отправки почтового уведомления после завершения задачи
Поток операций (Sequence Flow) служит для создания последовательности выполнения процессов. Для каждой операции (“Стрелочки”) задаются условия запуска следующего шага процесса
Шлюзы отвечают за ветвления или распараллеливания процесса по заданному условию
Триггеры позволяют прекратить или перенаправить процесс при наступлении определенного события. Например, часто мы используем триггер времени для того, чтобы обеспечить SLA по процессу.
Мы не будем останавливаться на том, как рисовать схему, так как это достаточно легко, и информацию можно легко найти в интернете. Вместо этого мы рассмотрим непосредственно те механизмы Activiti, которые позволяют интегрировать бизнес-процесс в платформу Unidata. Для того, чтобы процесс начал работать необходимо написать java-класс, который реализует интерфейс WorkflowProcessSupport, каждый метод которого отвечает за определенное событие процесса. Интерфейс WorkflowProcessSupport отвечает за взаимодействие с Activiti в платформе Unidata. Ниже примеры методов такой реализации.
@Override
public WorkflowProcessStartState processStart(StartProcessRequestContext ctx) {
WorkflowProcessStartState state = new WorkflowProcessStartState();
state.setAllow(true);
Map<String, Object> processVariables = state.getAdditionalProcessVariables();
if (processVariables == null) {
processVariables = new HashMap<>();
}
processVariables.put("validFrom", ctx.getValidFrom());
processVariables.put("validTo", ctx.getValidTo());
state.setAdditionalProcessVariables(processVariables);
return state;
}
Метод processStart вызывается перед началом старта задачи (userTask) внутри процесса. На этом этапе еще не создается task_id, но уже инициируются все параметры процесса и задачи.
@Override
public WorkflowProcessAfterStartState afterProcessStart(String processInstanceId, String
processDefinitionId, Map<String, Object> variables) {
WorkflowProcessAfterStartState state = new WorkflowProcessAfterStartState();
Map<String, Object> processVariables = state.getAdditionalProcessVariables();
if (processVariables == null) {
processVariables = new HashMap<>();
}
processVariables.put("processId", processInstanceId);
String initiatorEmail = (String) variables.get("initiatorEmail");
if (initiatorEmail == null || !initiatorEmail.contains("@"))
processVariables.put("initiatorEmail", "testmail@testmail.com");
state.setAdditionalProcessVariables(processVariables);
return state;
}
Метод afterProcessStart вызывается после старта задачи. В отличие от события processStart на этом этапе уже существует tasl_id, и мы можем взаимодействовать с задачей.
@Override
public WorkflowTaskCompleteState complete(String taskDefinitionId, Map<String, Object> variables,
String actionCode) {
WorkflowTaskCompleteState state = new WorkflowTaskCompleteState(true, "FINISHED".equals(actionCode) ? "Approve task." : "Decline task.");
Map<String, Object> additionalProcessVariables = state.getAdditionalProcessVariables();
if (additionalProcessVariables == null) {
additionalProcessVariables = new HashMap<>();
}
Map<String, Object> additionalTaskVariables = state.getAdditionalProcessVariables();
if (taskDefinitionId.equals("expertTask")) {
if (!WorkflowUtils.curatorsIsAvailable("role_name")) {
throw new WorkflowException(USER_NOT_AVALIBLE, ExceptionId.EX_WF_CANNOT_COMPLETE_TASK_COMPLETION_ERROR);
}
}
state.setAdditionalProcessVariables(additionalProcessVariables);
state.setAdditionalTaskVariables(additionalTaskVariables);
return state;
}
Метод complete вызывается после завершения задачи, то есть осуществляется переход от текущей задачи до следующей согласно схеме процесса.
@Override
public WorkflowProcessEndState processEnd(String processDefinitionId, Map<String, Object> variables) {
String selectorValue = (String) variables.get("finishTaskSelector");
WorkflowProcessEndState state = new WorkflowProcessEndState();
if (selectorValue != null && selectorValue.equals("complete")) {
state.setComplete(true);
} else {
state.setComplete(false);
}
LOGGER.info(String.format("End process %s with action %s", processDefinitionId, selectorValue));
return state;
}
Метод processEnd вызывается после завершению всего бизнес-процесса. Завершение бизнес-процесса может быть как положительным, когда процесс успешно завершился по всем задачам, или отрицательным, когда процесс может быть прерван на какой либо задаче.
Для того, чтобы описать логику в обозначенных методах, используются специальные переменные задач и процесса. Эти специальные переменные предоставляет сама Activiti, которые задаются в виде значений свойств компонентов Activiti. Например, это могут быть Form properties для UserTask.
![](https://habrastorage.org/getpro/habr/upload_files/c97/f1b/e22/c97f1be2239bb5bc1ae1432a93ffae13.png)
В данном примере существует переменная descriptionCode, которая будет хранить код описания задачи, а так же мы имеем переменную taskSelector, которая хранит список доступных решений при завершении задачи. В зависимости от выбранного решения процесс пойдет по определенному в схеме пути. В целом практически каждый бизнес-процесс имеет ветвления, которые регулируются шлюзами. Каждый шлюз принимает на вход параметр, который определяет по какому пути будет двигаться процесс.
Еще одна важная функция, которую следует учесть – это назначение исполнителя задачи. Для этого в параметрах элемента UserTask, в разделе Main config имеются параметры Assignee и Candidate group. Если заполнен параметр Assignee, то платформа назначает задачу на конкретного пользователя. Если заполнен параметр Candidate group, то задача становится доступна всем пользователям, состоящим в указанной группе, таким образом пользователи с определенной ролью смогут взять данную задачу на исполнение.
![](https://habrastorage.org/getpro/habr/upload_files/087/d03/267/087d03267aff42344016ea46ffcde6d2.png)
В простых случаях достаточно создать схему процесса и создать методы класса, чтобы бизнес-процесс заработал. В платформе администратор указывает для какой именно сущности должен запускаться процесс. Однако в нашей практике практически не бывает простых бизнес-процессов. Очень часто при переходе от задачи к задаче необходимо отправлять почтовые уведомления, делать проверки данных при завершении той или иной задачи, в зависимости от задачи изменять параметры процесса и другие. Поэтому кроме описания стандартных классов и построения схемы, разработчику необходимо напрямую обращаться к Activiti для реализации логики, специфичной для конкретной задачи. Для этого Activiti предоставляет Listener’ы, в которых указывается Java класс, описывающий логику конкретной задачи.
![](https://habrastorage.org/getpro/habr/upload_files/a6e/c21/9a1/a6ec219a1334a91635438797c35bc51f.png)
В целом все эти механизмы позволяют нам проектировать любой бизнес-процесс и успешно встраивать его в готовое производственное решение, но есть у Activiti и существенные недостатки, главный из которых – это организация хранения данных в БД. Дело в том, что на практике мы инициализируем и используем большое количество переменных как для задач бизнес-процесса, так и самого процесса. В Activiti нет отдельной таблицы для хранения переменных, вместо этого переменные записываются в отдельное поле основных таблиц хранения задач и процессов. Поэтому один процесс может иметь более сотни строк по каждой переменной. Когда количество бизнес-процессов достигает нескольких сотен тысяч и более, таблицы Activiti становятся чрезмерно массивными и стандартный поиск Activiti через SQL запросы в БД становится неэффективным. Для решения этой задачи платформа при каждой инициации процесса и задач, а так же после их завершения, дублирует данные Activiti в индексы elasticsearch. При поисковом запросы мы уже обращаемся к индексам эластика и не трогаем базу.
В целом, Activiti BPM позволяет легко и достаточно быстро разрабатывать любые бизнес-процессы и встраивать их в существующую инфраструктуру, но следует иметь в виду, что есть определенные проблемы с хранением данных в БД.