1. Rollback до першої правки коду
Вважайте, що pilot slice уже обрано: reports/* потрапив до MIGRATION_PLAN.md, branch або worktree створено, обсяг зрозумілий. Цього досить, щоб не чіпати весь сервіс, але недостатньо, аби робити першу правку. До першої правки потрібен rollback-план.
Після вибору pilot slice зазвичай виникає дуже небезпечне відчуття контролю: здається, що раз шматок маленький і безпечний, можна спокійно відкрити build.gradle, змінити версію Boot і подивитися, що вибухне. Саме в цей момент і починаються пригоди у стилі «ну ми думали, що швидко повернемо назад». Rollback-план потрібен саме для того, аби ці пригоди не стали жанром.
У migration є неприємна особливість: навіть маленька зміна майже ніколи не обмежується кодом. Змінюєте версію фреймворку — підтягнуться бібліотеки. Змінюєте бібліотеку — зміниться поведінка конфігурації. Змінюєте конфігурацію — ламаєте середовище запуску. Тому «чи можемо почати pilot?» іде поруч із «чи можемо швидко повернутися до baseline?». Формула проста:
Спочатку rollback, потім edit.
Звучить трохи менш романтично, ніж «спочатку хакнемо, потім розберемося», зате значно дешевше. У CashFlow Dashboard ми йдемо з Spring Boot 2.7 + Java 8 у бік Boot 3.x + Java 21. Pilot на read-only модулі reports чіпає залежності, збірку і старт. До першої правки фіксуємо письмово: що відкочується, хто підтверджує, за якими сигналами — «стоп».
Rollback у цьому сенсі — не документ для postmortem, а частина стартового майданчика. Немає його — ви не почали контрольовану migration; ви просто відкрили редактор із добрими намірами.
2. П’ять шарів відкату, а не тільки git revert
Коли розробник чує слово rollback, мозок часто автоматично підставляє git revert або git restore. Команди корисні, спору немає. Але migration ламається не тільки в Git-історії: іноді в залежностях, іноді в конфігурації, іноді в даних, а іноді в ланцюжку розгортання. Тому rollback корисно мислити не як одну кнопку, а як п’ять різних шарів.
Зведена карта має такий вигляд:
| Тип відкату | Що повертаємо назад | Типовий механізм | Приклад для CashFlow Dashboard |
|---|---|---|---|
| Code rollback | Змінені файли в репозиторії | |
Повернути reports/* і build.gradle.kts до baseline commit |
| Dependency rollback | Версії бібліотек і lockfile | Відновлення зафіксованих версій і lockfile | Повернути Spring Boot 2.7.18, відкотити gradle.lockfile |
| Config rollback | Флаги, env, yaml/properties | Повернути старі значення, вимкнути flag | Вимкнути reports.boot3_pilot_enabled, відновити старі actuator-paths |
| Data rollback | Зміни схеми або даних | backup, restore, rollback-script | Для read-only pilot — none, because ... |
| Deployment rollback | Раніше викладений артефакт | redeploy previous artifact | Повернути попередню збірку, якщо вона сумісна з поточними даними |
Найприємніший із цих п’яти — code rollback: часто вирішується засобами Git і займає лічені хвилини. Але далі починаються нюанси, яких таблиця не покаже.
Dependency rollback уже потребує дисципліни: без явних версій і lockfile «повернутися на попередній стек» перетворюється на вгадування. Той самий build.gradle без коректного lockfile підтягне інший граф залежностей — формально відкотилися, а фактично стоїте в трохи іншому світі.
Config rollback ще підступніший, бо багато конфігураційних правок здаються нешкідливими. Змінили шлях до actuator, увімкнули флаг pilot-сценарію — а потім ніхто не пам’ятає, що і де змінювали. Git допоможе з application.yml, але не скаже, хто вмикав флаг у staging і де env var у секретах CI.
Data rollback — найдорожчий. Гарна новина в тому, що наш зріз по reports read-only, тому тут чесно стоїть не порожнє n/a, а чітке пояснення: none, because pilot is read-only and does not change schema or seed data. Навіть незадіяний шар позначається явно.
Deployment rollback зазвичай здається простим — аж доки хтось не згадає, що попередній артефакт уже несумісний із поточною схемою або конфігом. Тоді «повернути стару збірку» не дорівнює «повернути робочий стан».
Добре практичне правило: на кожен із п’яти шарів — або конкретний спосіб відкату, або none, because .... Порожні секції — не акуратність, а запрошення до хаосу.
Невеликий фрагмент робочого документа може мати такий вигляд:
## Відкат даних
none, because pilot touches only `reports/*` read-only endpoints
and does not change schema, seed data or stored calculations.
## Відкат залежностей
restore Spring Boot version to `2.7.18`
and recover `gradle.lockfile` from baseline commit.
Ані поезії, ані туману. І чудово.
3. Abort conditions: вимірювані стоп-сигнали
Навіть найакуратніший rollback-план майже декоративний, якщо команда заздалегідь не домовилася, у який момент його застосовувати. Фраза «якщо стане погано, відкочуємо» звучить бадьоро, але по суті нічого не означає. Що таке «погано» — впав тест, флаконувся smoke check, скарга колеги в чаті? Тут дуже легко скотитися в довгі суперечки замість дій.
Тому поруч із rollback-планом завжди живуть abort conditions — заздалегідь оголошені стоп-сигнали: вимірювані, перевірювані, прив’язані до конкретного pilot scope. Різниця між звичайним упалим чеком і abort condition тонка, але важлива: failing check — сигнал «розберіться», abort condition — «розбиратися будемо вже після повернення до baseline». Тобто це не технічна помилка, а управлінська межа ризику.
| Розмита формулювання | Інженерно придатне формулювання |
|---|---|
| Якщо щось піде не так, відкочуємо | Якщо /reports/monthly віддає 5xx на staging після migration build — rollback |
| Якщо тести стануть нестабільними | Якщо MonthlyRevenueIT падає 3 прогони підряд на чистому середовищі — rollback |
| Якщо відповідь якось зміниться | Якщо JSON shape відрізняється від baseline snapshot — rollback |
| Якщо стане повільно | Якщо p95 latency зросла більш ніж на 30% на smoke-навантаженні — stop and review |
Для нашого pilot по модулю звітів розумно додати ще одне, якого немає в таблиці: feature flag не повертає систему до старої поведінки в staging. Видно, що це вже не «мені здалося», а спостережувані події.
Іноді в rollback-стратегію зручно додати feature flag — швидкий вимикач і дуже дешеву першу реакцію:
reports_engine:
boot3_pilot_enabled: false # швидке повернення до старого шляху
owner: dashboard-team
rollback_window_minutes: 30
Але тут важливо не переоцінити його магію: feature flag сам по собі не рятує від невдалого оновлення залежностей, не виправляє збірку, не відновлює дані. Це інструмент config rollback, а не універсальна паличка-виручалочка.
Коли формулюєте abort conditions, корисно поставити собі дуже приземлене запитання: чи зрозуміє інший інженер, який чергує о 2:17 ночі, коли саме відкочувати pilot, не телефонуючи вам? Якщо відповідь «ну, приблизно так» — умова ще сира.
4. Owner approval: відповідальні за відкат
На невеликих особистих pet-проєктах легко уявити, що owner завжди один і той самий чоловік — ви. У реальній migration навіть pilot швидко впирається у спільні ресурси: модуль в однієї команди, конфіг в іншої, CI/CD у третьої, база взагалі під окремою увагою. Не зафіксуєте — rollback перетвориться на гру «хто зараз достатньо сміливий, щоб натиснути кнопку».
Після попередніх модулів у вас уже є корисна звичка дивитися на ризик і права доступу заздалегідь. Migration за визначенням не належить до категорії «дрібних нешкідливих задач», тому в ROLLBACK.md пишемо не абстрактне «команда вирішить», а конкретного власника шару або хоча б конкретну роль.
Для нашого сценарію з CashFlow Dashboard картина може бути такою:
| Область | Хто підтверджує | Чому саме він |
|---|---|---|
| Код reports/* | tech lead модуля звітів | Він відповідає за поведінку pilot scope |
| Версії Boot / Gradle / lockfile | build/platform owner | Він розуміє сумісність збірки і залежностей |
| Feature flags і runtime config | owner сервісу або on-call | Він керує вмиканням конфігурації в середовищі |
| Зміни схеми даних | data owner | Лише він підтверджує rollback даних і backup |
| Повернення релізного артефакту | release/on-call engineer | Він володіє деплой-процесом |
Тут є важливий психологічний ефект: щойно ви записали owner, rollback перестає бути абстракцією — у документа з’являється адресат. А коли адресата немає, аварійні дії вживають ті, хто просто опинився ближче до клавіатури. Поганий критерій.
Ще одна тонкість: Claude Code допоможе зібрати чернетку, знайти конфіги, нагадати про lockfile, підказати власників за структурою репозиторію. Але рішення «відкочуємо» залишається людським. Це не недовіра до AI, а нормальна інженерна гігієна: у migration-агента немає повноважень самостійно вирішувати долю спільних ресурсів — максимум нагадає, що в ресурсу є власник.
5. Структура хорошого ROLLBACK.md
Тепер зберемо все в один робочий артефакт. Хороший ROLLBACK.md — не роман, не послання нащадкам і не чек-лист на двадцять екранів. Короткий, жорсткий і дуже конкретний документ на три питання: що відкочуємо, за якого сигналу і хто підтверджує.
На початку документа корисно зафіксувати контекст pilot’а: який slice мігруємо, у якій гілці або worktree, від якого baseline commit відштовхуємося, хто власник документа і коли його востаннє перевіряли. Звучить бюрократично — рівно до першого моменту, коли у вас відкрито три схожі гілки, дві з них названо майже однаково. Тоді підпис Branch: migration/boot3-pilot раптом виявляється однією з найкращих рядків у вашому житті.
Чернетковий каркас може бути таким:
# ROLLBACK.md
Pilot: `reports` Boot 3.x pilot
Branch: `migration/boot3-pilot`
Baseline commit: `a1b2c3d`
Owner: `@dashboard-tech-lead`
## Відкат коду
## Відкат залежностей
## Відкат конфига
## Відкат даних
## Відкат деплою
## Умови переривання
## Власники та схвалення
Після цього кожен розділ треба заповнювати не загальними побажаннями, а конкретикою. Хороша секція Code rollback називає файли і мінімальний прогін після відкату. Dependency rollback указує, де саме фіксується версія: build.gradle.kts, gradle.properties, gradle.lockfile, wrapper-конфіг. Config rollback називає feature flag, env var або config path. А незадіяний шар отримує не ліниве n/a, а чесне пояснення.
Невеликий фрагмент уже заповненого документа для нашого pilot може мати такий вигляд:
## Відкат коду
restore `reports/*`, `build.gradle.kts` and `gradle.lockfile`
from baseline commit `a1b2c3d`, then rerun `reports-it`.
## Відкат конфига
set `reports.boot3_pilot_enabled=false`
and restore previous actuator path settings in staging.
## Відкат даних
none, because pilot is read-only and does not touch schema or stored data.
Зверніть увагу на стиль: усе написано так, щоб інша людина взяла файл і виконала дії без телепатії. У цьому і полягає сенс.
Є ще одна невелика, але дуже корисна звичка — пов’язувати ROLLBACK.md із сусідніми артефактами: у шапці — посилання на MIGRATION_PLAN.md, в abort conditions — на smoke checks або integration suite. Тоді у вас не розсип Markdown-файлів, а зв’язаний пакет migration evidence.
6. Claude як помічник для rollback-плану
Коли мова заходить про артефакти на кшталт ROLLBACK.md, новачки іноді роблять дзеркальні помилки. Одні взагалі не підключають Claude Code — «це ж управлінський документ, AI тут не потрібен». Інші, навпаки, пишуть «зроби rollback plan» і сподіваються на чарівництво. Обидві крайнощі так собі: Claude корисний як дослідник і редактор, а не як той, хто ухвалює рішення.
Найкращий режим роботи — plan-first і read-only. Тобто ви не просите Claude щось відкотити, а просите підготувати чернетку за pilot scope, переглянувши код, конфіги й build-файли. Хороший запит може звучати так:
Review the pilot scope in `reports/*` and draft `ROLLBACK.md`.
Для кожної секції напиши:
- exact rollback action, or
- `none, because ...`
List measurable abort conditions and required owners.
Do not edit code. Do not start migration.
Такий запит хороший тим, що одразу задає формат мислення: Claude не йде в implementation і не ховає порожні місця за красивими словами. Або дія, або чесне «чому шар не бере участі».
Після цього починається найважливіша частина — людська перевірка. Вам потрібно пройтися чернеткою і поставити приземлені запитання. Чи відтворюваний dependency rollback і чи не забуто lockfile? Чи виконуваний config rollback без доступу до трьох систем? Хто саме є власником shared feature flag? Якщо десь написано «просто повернути попередню версію» — це не план, а побажання.
Дуже корисно також просити Claude окремо шукати прогалини, а не тільки писати текст: чи всі п’ять шарів закрито, чи не розмиті abort conditions, чи не забуті owner approvals. У цьому режимі він особливо добрий — виступає не як автор, а як педантичний другий читач, який рятує команду від красивого, але марного файлу.
І коли ROLLBACK.md після такого проходу стає коротким, зрозумілим і пов’язаним із реальним обсягом pilot-зрізу, migration раптом перестає виглядати як стрибок у темряву. Ризиковою вона бути не перестає. Але це вже контрольований ризик, а не надія на «ну Git же є, якось викрутимося».
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ