08.11.24

Camunda 7: Почти всё о миграции процессов

Мстислав Мартынюк
Автор:Мстислав Мартынюк
20 мин · Обучающие

Когда и зачем нужна миграция процессов?


Часто складывается ситуация, когда изменения в процесс вносятся по инициативе бизнеса, который ждет, что с момента совершения изменений ранее запущенные экземпляры процессов (Process Instances) будут работать по новым правилам. А иногда бывает наиборот – изменения носят технический характер и миграция не требуется. На этапе разработки изменений должно быть четко сформулировано требование - нужна миграция существующих инстансов или нет.


Матчасть


Миграция – перенос экземпляра процесса (инстанса) с исходной (Source) модели на целевую (Target). Переносить можно как со старой версии на новую, так и наоборот – с более свежей версии на предыдущую. Можно выполнять перенос даже между моделями процессов, имеющими разный Process Definition Key. Экземпляр процесса переносится в том состоянии, где находится токен процесса в момент выполнения миграции. А находиться он может только на элементе BPMN, порождающем состояние ожидания (Wait State):


  • Service Task (External); 

  • Receive Task;

  • User Task;

  • Message Catch Event;

  • Timer Event;

  • Signal Event;

  • Event Based Gateway;

  • Иные элементы, имеющие флаг Async before (after) = true.


Простая миграция Camunda

(Пример миграции процесса)


На диаграмме выше токен процесса находится на задаче "Say hello to demo", она будет являться точкой сохранения (Save Point), которая будет переноситься из version 1 процесса в version 2.


Инструкции по миграции (Migration Instructions) – сопоставление элементов исходной и целевой моделей процесса. Достаточно сопоставить между собой элементы, на которых может находиться токен. Элементы, имеющие одинаковые имена, могут быть сопоставлены между собой автоматически, в ходе генерации Плана миграции.


План миграции (Migration Plan) – набор инструкций по миграции и список переменных процесса, которые должны быть помещены в контекст процесса во время миграции.


План миграции генерируется при помощи Java API:  


  •  runtimeService.createMigrationPlan().build();


Или REST API:


  • POST /migration/generate



Флаг updateEventTriggers (Boolean) – задается в плане миграции и указывает на необходимость обновить триггеры прослушиваемых событий. Установка в true обновит все события, связанные с мигрируемыми процессами, но потребует больше времени на выполнение миграции.


Выполнение миграции – может выполняться как синхронно (метод .execute()), так и асинхронно, в фоне (метод .executeAsync()). Если число экземпляров процессов для миграции исчисляется тысячами и более, лучше воспользоваться вторым вариантом.


Для запуска миграции необходимо иметь готовый план миграции и запрос для отбора экземпляров процесса, которые будем мигрировать.


Если необходимо при выполнении миграции проигнорировать Execution Listeners и Input-Output  mapping, необходимо установить соответствующие флаги (.skipCustomListeners() и .skipIoMappings()).


С чего начать?


Первый шаг - анализ изменений. Необходимо понять, насколько трудоемкая миграция предстоит. В целом, миграции различаются по уровню сложности:


  • Техническая (простая) миграция;

  • Миграция с разработкой инструкций;

  • Сложная миграция.


Рассмотрим каждый сценарий подробнее.


Техническая (простая) миграция


Часто изменения процессов являются синтетическими. Открыли и сохранили диаграмму в новой версии моделера, исправили ошибку в наименовании задачи или разработчик не выспался и случайно подвинул элемент в диаграмме – любое изменение XML-кода повлечет за собой появление новой версии процесса с новым идентификатором (Process Definition ID).


При таких изменениях миграция не представляет большой сложности и заключается в:


  • Генерации плана миграции с автоматическим маппингом элементов (.mapEqualActivities()). При автоматическом маппинге, система самостоятельно сопоставляет элементы одинакового типа (например User Task), имеющие одинаковые ID. Пример для Java API приведен ниже. При необходимости, можно установить значения переменных процесса; 


MigrationPlan plan = runtimeService

       .createMigrationPlan(sourceDefinitionId, targetDefinitionId)

       .mapEqualActivities()

       .build();


  • Отборе экземпляров процесса для миграции и исполнении плана. Ниже - пример для Java API:


runtimeService.newMigration(plan)

       .processInstanceQuery(

           runtimeService

                   .createProcessInstanceQuery()

                   .processDefinitionId(sourceDefinitionId))

       .execute();



Аналогичным образом осуществляется миграция и в случае, если на диаграмме появились новые элементы или изменился поток управления.


Миграция с разработкой инструкций


Более сложным сценарием являются ситуации, когда:


  • Элементы диаграммы удаляются;

  • Вместо одних элементов появляются другие;

  • Меняются технические ID элементов (например, разработчик решил присвоить человекочитаемые ID задачам). 


Если вы попытаетесь выполнить автоматическую миграцию такого процесса, система выведет ошибку:


Cannot migrate activity instance 'say-hello': There is no migration instruction for this instance's activity


В этом случае придется вручную сопоставить между собой элементы исходной и целевой моделей процесса.


MigrationPlan plan = runtimeService

       .createMigrationPlan(sourceDefinitionId, targetDefinitionId)

       .mapActivities("say-hello", "say-hello-new")

       .mapActivities("say-hello-robot", "say-hello-robot-new")

       .build();




Сложные миграции


И наконец, самые трудоемкие для миграции изменения: 


  • С упразднением групп элементов или заменой элементов одного типа на другой (например, External Task вместо User Task);

  • Изменилась структура данных или процесс требует получения их извне.



При создании плана миграции система не даст вам сопоставить элементы разных типов и выдаст ошибку наподобие:


ENGINE-23001 Migration plan for process definition 'migration-process:2:ef80876f-9d10-11ef-b6e4-ae109a6d6cdf' to 'migration-process:8:c620f3dc-9d99-11ef-a362-feb28bfdf17a' is not valid:

Migration instruction MigrationInstructionImpl{sourceActivityId='say-hello', targetActivityId='say-hello', updateEventTrigger='false'} is not valid:

Activities have incompatible types (UserTaskActivityBehavior is not compatible with ServiceTaskExpressionActivityBehavior)


Решений в данном случае может быть два.


Migration Island


Исходная версия процесса. Обратите внимание на пользовательскую задачу "Input data". Она будет являться точкой сохранения и в момент миграции токен будет находиться на ней.


Исходная модель процесса


(исходная модель процесса)


В целевой версии процесса пользовательская задача "Input data" заменена на сервисную задачу "Get data from external API". 


Целевая модель процесса

(целевая модель процесса)


Помимо того, что Camunda не сможет выполнить миграцию с пользовательской задачи на сервисную, ситуация осложняется тем, что для выполнения задачи "Get data from external API" необходимы данные, отсутствующие в бизнес-процессе. Решение - предусмотреть в целевой модели процесса так называемый Migration Island - набор элементов, выступающих в поле "посадочной площадки" для токена переносимого процесса.



Migration Island

(Migration Island)


После переноса инстансов процесса на целевую модель, при помощи API выполняется автоматическое завершение Migration Task, после чего процесс продолжает выполняться привычным образом.


Промежуточное описание процесса


Второй способ представляет собой организацию промежуточного описания процесса примерно следующего вида:


Миграция - промежуточная модель процесса



Миграция осуществляется в два этапа:


  1. Перенос инстансов процесса на промежуточную модель (токен "приземляется" на задачу "Input token", после чего она завершается).

  2. Перенос с промежуточной модели на целевую (токен перемещается с задачи "Output token" на задачу в целевом процессе).


Высоконагруженные системы


Миграцию значительного числа инстансов на высоконагруженной системе предпочтительно выполнять по следующему алгоритму, чтобы не создавать дополнительную нагрузку на Camunda:


  1. Отобрать 500-1000 экземпляров процесса, подлежащих миграции;

  2. Приостановить их выполнение (suspend);

  3. Выполнить асинхронную миграцию (через migration island или промежуточную модель);

  4. Активировать их выполнение (activate)

  5. Завершить пользовательские задачи (если требуется);

  6. Проверить, остались ли еще экземпляры процесса, требующие миграции, если да, перейти к пункту 1.