Миграция с Camunda 7 на Camunda 8: какую поддержку дает ИИ?

Источник: Camunda•

Миграция с Camunda 7 на Camunda 8: какую поддержку дает ИИ?

ИИ может сделать миграцию с Camunda 7 на 8 быстрее и дешевле, но более чистый результат не гарантирует корректность. В ходе четырех запусков на одном примере приложения полностью агентский запуск на базе Sonnet использовал на 40% меньше входных токенов и завершился примерно вдвое быстрее связки…

Некоторое время назад Лана и Питер написали блог-пост, объясняющий, как мигрировать решение с Camunda 7 на Camunda 8 с помощью агентского ИИ и навыка миграции Camunda для поддержки этого процесса. Этот пост вызвал у меня любопытство, и я захотел проверить, чего способен достичь этот ИИ-навык. В частности, мне хотелось сравнить, насколько хорошо этот навык умеет не просто запускать конвертер диаграмм и рецепты OpenRewrite, а выполнять по-настоящему агентскую миграцию, используя ИИ для переноса моделей BPMN и кода на Java. Я хотел сопоставить результаты систематической и агентской миграции, а также сравнить результаты агентских миграций с использованием разных моделей ИИ.

Навык: ИИ как вспомогательное средство управления

Инструмент представляет собой навык camunda-migration:migrate-c7-to-c8-code, официально предоставляемый Camunda (в данном случае версии 0.1.0 из репозитория GitHub camunda/camunda-7-to-8-migration-tooling). Он устанавливается в Claude Code и настраивается на работу с директорией проекта решения на C7.

То, что происходит дальше, поначалу выглядит не слишком впечатляюще, и тем не менее именно эта часть экономит больше всего времени. Навык проводит инвентаризацию проекта: он обходит классы Java, классифицирует их на связующий код (glue code), клиентский код и конфигурацию, читает файлы BPMN, собирает используемые API C7 и оценивает сложность каждого файла. Для примера решения это дает инвентаризацию, суть которой укладывается в несколько строк:

CalculateDoWait.java (JavaDelegate + Client Code, High). Реализует JavaDelegate и внедряет RuntimeService для запуска createProcessInstanceQuery()...list().size(). В C8 это превращается в поиск экземпляра процесса через CamundaClient в кластере оркестрации, причем этот поиск является в конечном итоге согласованным (eventually consistent).

Затем навык сам загружает инструменты: он скачивает подходящую версию CLI-конвертера диаграмм, проверяет версию Java, при необходимости подключает плагин OpenRewrite в сборку Maven, запускает преобразования, считывает списки результатов и обрабатывает выявленные проблемы. В конце он записывает файл MIGRATION_REPORT.md, содержащий опись, журнал решений, таблицу всех изменений и список нерешенных вопросов.

До этого момента ИИ выступает исключительно как средство управления уже существующими инструментами. Это отличие заметно на практике, но это еще не самое интересное.

Два пути, четыре запуска

Самое интересное заключается в том, что навык предлагает два пути для каждой из двух основных областей работы:

Модели (BPMN/DMN)

CLI-конвертер диаграмм с последующей очисткой с помощью ИИ

ИИ переписывает XML напрямую

Код Java

Рецепты OpenRewrite с последующей очисткой с помощью ИИ

ИИ мигрирует все самостоятельно

Чтобы выяснить, что этот выбор означает на практике, мы мигрировали тестовое решение четыре раза, причем каждый раз начиная с абсолютно одинакового состояния на C7:

CLI-конвертер

OpenRewrite + очистка силами ИИ

Sonnet 5

CLI-конвертер

Только ИИ

Sonnet 5

агентский

Только ИИ

Sonnet 5

агентский

Только ИИ

Opus 5

Эта схема отвечает на два разных вопроса. Запуски с 1 по 3 были выполнены на одной и той же модели и различаются только по методу — это сравнение методов, а поскольку токены напрямую пересчитываются в деньги, это также сравнение затрат. Запуск 4 настроен точно так же, как запуск 3, и отличается только используемой моделью ИИ — это сравнение моделей.

Первый вопрос: во сколько обходится каждый путь

Дешевле не всегда значит лучше, поэтому вот что вы получаете за свои деньги.

В работе с кодом ИИ явно побеждает

Начнем с того, где разница наиболее очевидна. SampleJavaDelegate после OpenRewrite с последующей очисткой силами ИИ (запуск 1) и полностью агентский подход (запуск 2):

Оба варианта работают. Но executeJobMigrated — это артефакт инструмента, который ничего не описывает; обращение к job.getVariable("x") обходит инъекцию переменных, которую клиент предоставляет в C8; конструкция HashMap излишльна. Ничто из этого не является ошибкой, просто это код, который никто не стал бы писать таким образом. И любому, кто будет читать его через два года, сначала придется догадываться, что здесь потрудился инструмент. Примечателен здесь не столько вывод самого рецепта (то, что он выглядит шаблонным — его законное право), сколько тот факт, что обещанная очистка силами ИИ эти места не затронула.

Картина с моделями неоднозначна

Для моделей процессов сравнение поначалу выглядит аналогичным образом. Сгенерированные агентским способом модели выглядят заметно аккуратнее:

Строки / байты

185 / 11 812

164 / ~8 860

Примечания conversion:message в модели

zeebe:header со старыми выражениями C7

xmlns:camunda, expressionLanguage="XPath"

остаются

удалены

Лишние файлы в src/main/resources

analysis-results.csv / .md

отсутствуют

Элементы conversion:message представляют собой замечания проверяющего инструмента. Они не мешают выполнению, но попадают в развернутую модель и в репозиторий — и здесь очистка силами ИИ в запуске 1 тоже не удалила их. Аналогично, файлы analysis-results остаются в директории ресурсов и попадают в JAR.

За исключением того, что чистота — не самый важный критерий для процессной модели. Подробнее об этом чуть ниже.

А как насчет токенов?

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

1 — детерминированный

Sonnet 5

10,08 млн

50 800

2 — BPMN-конвертер / агентский Java

Sonnet 5

7,87 млн

44 600

3 — полностью агентский

Sonnet 5

6,02 млн

46 400

4 — полностью агентский

Opus 5

5,69 млн

61 700

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

Причина кроется в колонке «Ответы» (Responses). Каждый ответ повторно отправляет весь накопившийся к этому моменту контекст, поэтому потребление входных данных растет не линейно по мере увеличения количества шагов, а примерно квадратично. Запуску 1 потребовался 81 ответ вместо 54, потому что результаты работы конвертера и рецептов сначала нужно было прочитать, затем интерпретировать, а затем переработать — и каждый из этих результатов затем остается в контексте до конца сессии. Агентский путь читает исходные файлы один раз и сразу записывает результат.

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

Второй вопрос: корректен ли результат?

Этот тезис верен для кода. Но не для моделей.

Для конвертации моделей эталоном выступает Diagram Converter, поскольку он реализует официальный набор правил. И в двух критически важных с точки зрения семантики точках агентский запуск 3 отклоняется от него:

Условный поток на шлюзе

=decision.wait

decision.wait (пропущен знак =)

=decision.wait

Вызывающая активность (Call activity)

propagateAllChildVariables="false"

атрибут отсутствует

установлен

Обе проблемы являются реальными багами, а не вопросами стиля.

Zeebe оценивает выражение как FEEL только в том случае, если оно начинается с =. Без знака равенства decision.wait является статической строкой, а не булевым значением — условие на шлюзе никогда не будет вычислено корректно. Оставляемый вместо этого запуском 3 атрибут language="feel" является конструкцией C7, и Zeebe просто игнорирует его. Модель успешно развертывается, но сам процесс продолжает выполняться некорректно.

Второй нюанс еще незаметнее. В Camunda 8 call activity по умолчанию передает все переменные подпроцесса обратно в родительский процесс. В оригинале C7 не было camunda:out, поэтому ничего не возвращалось. Без явного указания propagateAllChildVariables="false" это поведение меняется — никаких сообщений об ошибках или предупреждений, просто в родительском процессе появляются другие переменные.

Выполнение 4 принимает такое же решение, как и детерминированный конвертер, в обоих пунктах. И на этом можно констатировать главный вывод данной серии тестов: качество агентного подхода зависит от модели, а качество детерминированного — нет.

Воркер задач для компонента Spring

Различия продолжаются и в коде. Пример решения содержит выражение JUEL, которое вызывает компонент Spring (#{sampleBean.someMethod(y)}). FEEL так не умеет, поэтому ему нужен воркер задач. Все четыре прогона это осознают, но только один реализует это чисто.

Прогоны с 1 по 3 добавляют аннотацию @JobWorker прямо на класс SampleBean. Из-за этого чисто доменный класс получает зависимость от Camunda. Прогон 4 вместо этого создает собственный тонкий адаптер и оставляет SampleBean без изменений:

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

Остается один нюанс: прогон 4 переименовывает тип задачи из sampleBeanSomeMethod в sampleBean в процессе. Модель и код соответствуют друг другу, но имя отличается от того, что назначил бы официальный конвертер, и, соответственно, от того, чего ожидала бы команда при миграции частей этого же решения с помощью конвертера.

Счетчик экземпляров

Второй критический участок в коде — это класс, который использует runtimeService.createProcessInstanceQuery() для подсчета запущенных экземпляров и вычисления четно-нечетного значения на их основе. Все четыре прогона распознают, что этот запрос возвращает только работающие экземпляры в C7, и добавляют фильтр .state(ProcessInstanceState.ACTIVE) в C8. Когда дело доходит до чтения результата, они расходятся:

items() возвращает только совпадения с текущей страницы, а лимит по умолчанию для поискового запроса из Java-клиента составляет 100. Пример решения запускает ровно 100 экземпляров. Таким образом, счетчик упирается ровно в этот лимит, и решение меняется на противоположное, как только работающих экземпляров становится больше, чем помещается на одной странице. page().totalItems(), напротив, возвращает общее количество совпадений.

Это именно тот тип багов, который не ловит ни один компилятор, из-за которого не краснеет ни один тест и который никогда не проявляется в среде разработки с пятью экземплярами.

Прогон 4 также является единственным, который документирует, что этот поиск выполняется по вторичному хранилищу в C8 и поэтому является в конечном итоге согласованным (eventually consistent). Экземпляры, которые были запущены только что, могут отсутствовать. Это нельзя исправить во время миграции, это вопрос архитектуры. Но это именно то примечание, которое необходимо при код-ревью.

Есть и меньшие различия: прогон 4 отмечает переменную x в SampleJavaDelegate как optional = true и документирует каждый класс комментарием Javadoc. Прогон 3, напротив, включает converted-c8-*.dmn в список развертывания, хотя в проекте нет ни одного файла DMN.

А что с расходами?

Очевидное опасение, что более мощная модель увеличивает расход ресурсов, не подтверждается. Прогон 4 использует наименьшее количество входных токенов среди всех четырех — 5,69 млн. Ему требуется меньше шагов, но они более длительные и тщательные, что заметно по суммарным 52 ответам при наибольшем объеме вывода. Зато выполнение занимает заметно больше времени — 26 минут против 15 минут у прогона 3.

Тем не менее, это не переводится напрямую в сравнение затрат, потому что токены Opus стоят дороже токенов Sonnet. В рамках прогонов с 1 по 3 количество токенов напрямую отражает стоимость, но при переходе к прогону 4 они служат лишь мерой эффективности подхода.

Что сделали правильно все четверо

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

Все четверо признали, что в Camunda 8 больше нет неявного развертывания из classpath и требуется явная аннотация @Deployment, которая включает конкретно только сконвертированные модели. Все четверо увидели, что это приведет к состязанию (race condition) между инициализатором процесса и развертыванием, и переключили его с ApplicationRunner на @EventListener(CamundaPostDeploymentEvent.class).

И все четверо столкнулись с одной и той же проблемой зависимостей и разрешили ее: BOM в Spring Boot 3.5.16 управляет версией httpclient5, которая не совместима с клиентом Camunda 8.9, поэтому без переопределения создание компонента CamundaClient завершается с ошибкой NoSuchMethodError во время выполнения. Обнаружено, проверено через mvn dependency:tree, задокументировано.

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

Взгляд на перспективу

Агентный подход выигрывает по стоимости и качеству кода, но окупится ли это, зависит от размера проекта и того, сколько времени ваша команда может выделить на ревью.

Одна цифра для масштаба: полная миграция этого решения (инвентаризация, конвертация моделей, код, конфигурация, валидация и отчет) заняла от 15 до 29 минут. Контрольный ручной прогон не выполнялся, поэтому это не измеренная экономия времени. Но каждый, кто хоть раз переносил приложение Spring Boot с Camunda 7 на 8, может сам оценить порядок величины: только одна вышеупомянутая проблема с зависимостями стоит полдня работы без предварительных знаний.

Для сопоставления подходов все карты теперь на столе. Что касается кода, чисто агентный подход в этой серии тестов явно превосходит остальные: более идиоматичный, чистый, дешевый, быстрый. Что касается моделей, агентный вывод чище, а детерминированный — надежнее. Тот, кто не может или не хочет работать с более мощной моделью, безопаснее себя чувствует с конвертером, расплачиваясь за это доработками.

То же самое относится и к сравнению моделей между собой. Рабочий процесс тот же самый, и прогон использует даже меньше входных токенов, но стоимость токена выше. На чаше плюсов — две предотвращенные ошибки моделирования, четко разделенный воркер задач и корректно считываемый счетчик экземпляров.

Окупится ли это, зависит от проекта. С двумя файлами BPMN и пятью классами разница устраняется за час ревью — при условии, что кто-то при ревью знает, что выражение FEEL требует знака =. В случае решения из тридцати процессов и двухсот классов математика выглядит иначе, причем в обеих направлениях: трудозатраты на ревью растут, но растет и вероятность того, что одно из таких скрытых отклонений ускользнет. Мы намеренно не принимаем это решение ни за кого. Метрики и конкретные выводы на столе, выбор остается за каждым отдельным проектом.

И последний момент, который актуален независимо от подхода и модели: ревью никогда не является необязательным. Ни один запуск в этой серии тестов не ответил на по-настоящему сложные вопросы: имеют ли решения по-прежнему смысл в качестве бизнес-логики в условиях конечной согласованности (eventual consistency), что происходит с границами транзакций, имело ли значение для бизнеса значение historyTimeToLive в 180 дней. Запуски корректно задали эти вопросы и зафиксировали их в отчете. На них должны отвечать люди, которые понимают предметную область.

Ключевые выводы

Мигрированный процесс работает на Zeebe, механизме автоматизации процессов, лежащем в основе Camunda 8. Zeebe обеспечивал выполнение мигрированных процессов в ходе тестовых прогонов, поэтому вопрос никогда не стоял в том, сможет ли движок их запустить, — только в том, говорили ли ему модели правильные вещи. Конверсия выполняет механическую работу. Ревью определяет, соответствуют ли мигрированная семантика вашей первоначальной задумке.

  • Навык camunda-migration:migrate-c7-to-c8-code надежно берет на себя механическую часть: инвентаризацию, получение инструментов, конвертацию, конфликты зависимостей, отчет. Это сработало во всех четырех запусках.
  • Для кода чисто агентная миграция дает лучшие результаты, чем OpenRewrite с последующей очисткой с помощью ИИ, и требует на 40% меньше входных токенов при вдвое меньшем времени выполнения на той же модели.
  • Для моделей детерминированный Diagram Converter является более надежным путем, если только не используется более мощная модель. Два отклонения в запуске 3 привели бы к скрытым багам в продакшене.
  • Качество агентного пути зависит от модели; качество детерминированного пути — нет. Эта асимметрия является главным выводом данной серии тестов.
  • Тот факт, что агентная миграция достигает такого уровня, является новым. Sonnet 5 и Opus 5 были выпущены с небольшой разницей во времени, но Anthropic классифицирует их как разные уровни возможностей, и соответственно их стоимость в токенах отличается примерно в два раза. Эта разница в уровнях проявляется в результате («два скрытых семантических бага» против «ни одного»). Стоит ли это разницы в цене — каждый должен решить для себя.
  • Ревью остается обязательным. Инструменты и ИИ справляются с конвертацией; решение о том, соответствуют ли мигрированная семантика задуманной, остается за командой.

Поддержка ИИ в разработке программного обеспечения в настоящее время развивается очень быстро. Тест для этого поста показал, что более новая или более способная модель оказывает заметное влияние на качество мигрированного кода при в остальном идентичной конфигурации. Вероятно, и в будущем будет полезно полагаться на самые современные из доступных моделей или даже на модели, специально предназначенные для программирования на Java. В любом случае, перед масштабной миграцией стоит сравнить результаты работы различных актуальных моделей ИИ на репрезентативной выборке мигрируемого кода, точно так же, как это было сделано здесь в небольшом масштабе.

Этот пост написан партнером Camunda ilume Informatik AG.

О чём эта статья

Ещё в разделе «Разработка ПО»

Все →

Ещё от Camunda