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. Чим більше свободи у моделі, тим дорожчий rollback. Це формулюється в сесії або інструкції:
Працюй лише в гілці 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` + перевірочний GET `/reports/monthly`
Якщо хочеться, сюди ж можна додати owner і дату, особливо якщо проєктом займається не одна людина. Здається бюрократією — рівно до першого запитання «хто вирішив починати з цього файла?». Документ перетворює розпливчасте обговорення на рішення.
Хороший MIGRATION_PLAN.md у цій точці робить головне: міграція перестає бути лячною абстракцією. Ще жоден import не замінено — але це вже не «колись ми перенесемо сервіс на Boot 3», а обмежений і перевірюваний експеримент у конкретній папці, на конкретних файлах. Залишається один обовʼязковий крок до першої правки: зафіксувати rollback.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ