1. Execution: отдельный этап, а не обновление вслепую
Когда человек впервые слышит слово migration, рука сама тянется к файлу зависимостей: очень хочется открыть build.gradle, поменять версии и посмотреть, что загорится красным. Импульс понятный, но для финансового сервиса он примерно так же полезен, как чинить проводку ударом молотка: звук есть, уверенности — нет.
Execution — это не «начали править код», а перевод плана в контролируемый эксперимент. На migration discovery мы собирали карту несовместимостей. У execution задача другая: взять маленький, изолированный и дешёвый фрагмент и проверить на нём, что план живой. Не всю подсистему, не «давайте сразу весь billing». Такой фрагмент называется pilot slice.
Для CashFlow Dashboard это особенно важно. Переход с Boot 2.7 на Boot 3.x затрагивает не одну кнопку и не один import: это и javax.* → jakarta.*, и изменения в конфигурации Spring Security, и новый baseline по Java, и потенциальные изменения в наблюдаемости. Проглотите всё разом — получите огромный diff и вывод «миграции — это боль». Боль не в миграции, а в том, что вы зашли слишком крупно.
Нормальная инженерная логика выглядит так:
flowchart TD
A[COMPATIBILITY_MATRIX.md] --> B[Проверяем preconditions]
B --> C[Выбираем pilot slice]
C --> D[Создаём branch или worktree]
D --> E[Только потом меняем код]
На входе у вас уже есть два артефакта: COMPATIBILITY_MATRIX.md и черновик MIGRATION_PLAN.md. Дальше маршрут простой: выбираете slice, изолируете в branch/worktree, фиксируете rollback, делаете один узкий migration diff и доказываете parity в MIGRATION_VALIDATION_REPORT.md.
В этой схеме есть важная мысль: execution начинается до первого изменения в коде. Пока не ответили — на чём делаем pilot, в какой изоляции, чем проверим результат, — вы не исполняете migration. Вы хаотично двигаете версии.
2. Три preconditions для выбора pilot slice
Когда вы выбираете первый pilot, не нужно изобретать чек-лист на тридцать пунктов. В большинстве реальных случаев хватает трёх базовых фильтров: чем поймаете поломку, сколько будет стоить ошибка, не собьёт ли параллельная работа команды. Но это именно фильтр выбора slice, а не полный набор стартовых условий перед первой правкой.
| Preconditions | Зачем это нужно | Как это выглядит в CashFlow Dashboard |
|---|---|---|
| Есть тесты или их можно быстро добавить | Иначе вы не докажете, что pilot что-то сохранил | У reports/* есть integration-тесты, а у billing/* их мало и они хрупкие |
| В выбранном фрагменте нет high-risk business logic | Ошибка должна стоить дёшево | Read-only endpoint для отчётов безопаснее, чем InvoiceProcessor |
| По этим файлам нет concurrent changes | Иначе вы не поймёте, чей именно diff что сломал | Никто параллельно не меняет тот же контроллер или конфиг |
Первое условие — наличие тестов — кажется очевидным, но его часто обходят фразой «ну мы же руками проверим». В migration это слабое утешение: тесты не требуют покрытия 90% — достаточно, чтобы у pilot было чем ловить поломку; иначе зелёный результат держится на ощущении. High-risk logic отсекается, чтобы первая итерация отвечала «мы вообще умеем переносить этот сервис?», а не «переживём ли инцидент в биллинге в пятницу вечером»: плохой первый кандидат — payments, billing, refunds, сложные конфиги Spring Security. А concurrent changes кажутся мелочью, пока вы не увидите два коммита в один файл — один про migration, второй про «маленькую бизнес-правку» — и отладка не станет детективом. Migration любит спокойную воду.
Техническая проверка перед выбором pilot:
git fetch origin # обновляем локальное состояние
git log --oneline -- reports/MonthlyRevenueController.java | head # смотрим историю файла
./gradlew test --tests 'com.cashflow.reports.*' # есть чем ловить поломку
git status # рабочее дерево должно быть чистым
git branch --show-current # pilot не делаем в main
Заметьте, здесь нет ни одного шага «обновить Spring Boot». И это правильно: сначала убеждаемся, что имеем право брать кусок в pilot.
Отдельно полезно запомнить ещё одну вещь. Дополнительные preconditions существуют: generated code, кастомные обёртки над библиотеками, неизвестные версии плагинов, скрытые конфиги — расширенный список для сложных случаев. Но превратите каждый pilot в аудит на полдня — migration застынет на «мы всё ещё анализируем». Три базовых фильтра — не бедность мысли, а инженерная минимальность.
3. Хороший pilot — это где вы учитесь мигрировать
Теперь у вас есть preconditions, но остаётся самый неприятный вопрос: что именно брать первым? Здесь и рождаются все драматические решения в духе «а давайте сразу весь billing». Не надо. Хороший slice — не самый важный кусок системы, а тот, где вы получите максимум знаний при минимальной стоимости ошибки. Идеал — read-only endpoint или 1–3 файла, затрагивающие миграцию фреймворка, но не деньги, авторизацию и секреты.
| Кандидат | Подходит для первого pilot? | Почему |
|---|---|---|
|
Да | read-only flow, есть тесты, бизнес-риск умеренный |
|
Да, если рядом есть проверки | можно увидеть реальные проблемы с Boot 3, не ломая биллинг |
|
Нет | high-risk business logic, дорогая ошибка |
|
Нет | затрагивает деньги и фоновые процессы |
|
Скорее нет для первого pilot | слишком большой радиус поражения |
Хороший pilot — место, где вы учитесь мигрировать. Плохой — где сразу сдаёте экзамен без черновика.
И вот здесь Claude Code действительно полезен, но не как «миграционный волшебник», а как аналитик, читающий кодовую базу по фактам. Формулируйте запрос не «мигрируй модуль», а так, чтобы он помог выбрать кандидатов и, что важнее, отсеять неподходящие:
Сначала проанализируй модуль reports/ как кандидата для pilot migration.
Ничего не меняй.
Верни:
1) 1–3 файла, подходящие для первого pilot,
2) файлы, которые нужно исключить,
3) причины исключения,
4) ссылки на тесты, конфиги и зоны риска.
Почему здесь важен пункт про исключения? Потому что инженерное решение — почти всегда не только «что делаем», но и «что сознательно не делаем». Вернул хороший контроллер, но не объяснил, почему лежащий рядом InvoiceProcessor трогать нельзя, — ответ не готов к решению.
Ещё один практический критерий: slice должен ложиться в MIGRATION_PLAN.md одной-двумя фразами. Хороший: «read-only endpoint monthly report, один контроллер, один сервис, один набор тестов». Плохой: «подсистема отчётов в целом». В слове «в целом» живёт хаос.
4. Branch и worktree: физическое место для pilot
Для migration нужна изоляция. Даже идеально выбранный pilot всё ещё можно испортить одним простым способом: делать его прямо в основной ветке. Формально никто не запрещает, но на практике проект превращается в квартиру, где ремонт идёт сразу на кухне, в ванной и почему-то ещё на лестничной клетке. Migration нужна отдельная рабочая зона.
Если говорить совсем просто, branch — это отдельная линия истории Git. А worktree — ещё и отдельная физическая папка проекта, привязанная к своей ветке. Для обычной фичи хватает branch; для migration worktree держит рядом две рабочие копии: одна на старом стеке, другая на pilot с новым. Основной CashFlow Dashboard на Boot 2.7 в текущей папке, рядом ../cashflow-boot3 с pilot на Boot 3.x. Main не ломается, две версии сравниваются бок о бок. Роскошь, которая стоит дёшево.
Минимальный сценарий выглядит так:
git worktree add ../cashflow-boot3 migration/boot3-pilot # отдельная папка и ветка
cd ../cashflow-boot3
./gradlew test --tests 'com.cashflow.reports.*' # гоняем безопасный pilot
git status # изменения живут отдельно от main
git worktree list # видно обе рабочие копии
Если worktree пока кажется чем-то непривычным, ничего страшного. Полезная аналогия такая: branch — это альтернативная линия сюжета, а worktree — отдельный стол, на котором она лежит. Второй стол спасает и от путаницы, и от соблазна «чуть-чуть поправить» соседний модуль: имя папки migration/boot3-pilot напоминает, что вы здесь не для широкого рефакторинга, а для конкретного эксперимента.
5. Узкие разрешения для migration-сессии
В migration широкие разрешения вредят сильнее, чем помогают. На этом этапе у новичков часто появляется соблазн: раз охват вроде бы понятен, можно попросить Claude Code «сделать migration аккуратно». Звучит заманчиво, но именно здесь ограничить нужно не только модель, а и собственный энтузиазм.
Хорошая сессия начинается с жёсткой рамки. Claude работает только в migration-ветке, только по выбранному slice, только в тестовом окружении, без секретов, без deployment-команд, без захода в payments/, billing/ или .env. Чем больше свободы у модели, тем дороже откат. Это формулируется в сессии или инструкции:
Работай только в ветке migration/boot3-pilot.
Меняй только файлы модуля reports/ и связанные тесты.
Не трогай payments/, billing/, migrations/ и .env.
Не выполняй deployment-команды и не работай с секретами.
Перед правками сначала покажи план и список файлов.
После изменений перечисли проверки, которые нужно запустить.
Заметьте, здесь нет никакой магии — это не «идеальный промпт», а обычный инженерный контракт. В неинтерактивном режиме Claude Code или в настройках агента точные флаги зависят от версии — их стоит сверять с актуальной справкой. Устойчивый навык не в запоминании флага, а в умении сузить область работы.
И это касается не только Claude. Даже человек во время migration склонен думать: «Раз уж я здесь, заодно поправлю ещё вот эту мелочь». Вот это «заодно» особенно опасно. Пилот полезен, только когда его diff объясняется одной фразой. Всё, что не отвечает «умеем ли мы переносить этот slice на новый стек», оставляйте за пределами эксперимента.
6. Фиксация pilot в MIGRATION_PLAN.md
Когда pilot выбран, изолирован и ограничен, важно сделать ещё один взрослый шаг: записать его в MIGRATION_PLAN.md, а не оставить в реплике «ну мы вроде решили брать reports». Там появляется конкретика — что переносим первым и почему именно это.
Хорошая запись короткая, но плотная — она объясняет целевой slice, причину выбора, что сознательно исключено и где работа живёт физически:
## Pilot slice
- target: `reports/MonthlyRevenueController.java`
- rationale: read-only endpoint, есть integration-тесты
- excluded: `billing/InvoiceProcessor.java` — high-risk business logic
- branch: `migration/boot3-pilot`
- worktree: `../cashflow-boot3`
- baseline checks: `reports` test suite + smoke GET `/reports/monthly`
Если хочется, сюда же можно добавить owner и дату, особенно если проектом занят не один человек. Кажется бюрократией — ровно до первого вопроса «кто решил начинать с этого файла?». Документ превращает расплывчатое обсуждение в решение.
Хороший MIGRATION_PLAN.md в этой точке делает главное: миграция перестаёт быть пугающей абстракцией. Ещё ни один import не заменён — но это уже не «когда-нибудь мы перенесём сервис на Boot 3», а ограниченный и проверяемый эксперимент в конкретной папке, на конкретных файлах. Остаётся один обязательный шаг до первой правки: зафиксировать rollback.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ