1. Ви мігруєте не рядок версії, а мережу залежностей
Коли розробник уперше чує слова dependency graph, у голові інколи вимальовується монструозна схема із сотні коробочок і стрілок, від якої хочеться закрити ноутбук і піти вирощувати помідори. На практиці все набагато прозаїчніше. Граф потрібен для одного: перестати гадати, що ви оновлюєте — одну бібліотеку, ланцюжок бібліотек чи інструмент збирання, від якого залежить усе інше.
На прикладі CashFlow Dashboard це особливо помітно. Здається, ви хочете «просто оновити Spring Boot». Але Boot не живе у вакуумі: він тягне Spring Framework, security-шар, Jackson, валідацію, частину тестової інфраструктури, конфігураційні ключі, іноді — поведінку Gradle-плагіна. Оновіть один рядок версії в build.gradle, решту вважатимете дрібницею — і це вже не міграція, а технолотерея.
Тут корисно розрізняти два артефакти:
| Артефакт | На яке запитання відповідає |
|---|---|
| Dependency graph | Хто від кого залежить і через який ланцюжок це потрапляє в проєкт |
| Compatibility matrix | Що ця залежність означає для міграції: чи безпечно оновлювати, що зламається, чи потрібен проміжний крок |
Прямі залежності — це те, що ви явно написали в build.gradle. Транзитивні залежності — це те, що прийшло «в навантаження». Якщо прямі залежності — це гості, яких ви запросили на вечірку, то транзитивні — друзі гостей, які прийшли разом із ними, а потім ще привели своїх знайомих. І зазвичай саме вони створюють найцікавіші розмови на кухні.
Наприклад, прямі залежності CashFlow Dashboard можуть виглядати цілком невинно:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.postgresql:postgresql'
}
Але якщо подивитися трохи глибше, ви побачите вже ланцюжок:
+--- org.springframework.boot:spring-boot-starter-web -> 2.7.18
| \--- com.fasterxml.jackson.core:jackson-databind -> 2.13.x
+--- org.springframework.boot:spring-boot-starter-security -> 2.7.18
| \--- org.springframework.security:spring-security-config -> 5.7.x
\--- org.postgresql:postgresql -> 42.5.x
Ось тут і починається зріла інженерія. Міграція ламається не на слові web starter як такому, а на ланцюжку сумісності між Java, Gradle, Boot, Security, Jackson, JPA і вашим власним кодом. Тому перший корисний висновок цієї лекції звучить нудно, але дуже практично: ви мігруєте не рядок версії, а мережу залежностей.
2. Спочатку evidence, потім інтерпретація
Коли граф уже отримано, виникає друге запитання: як його взагалі читати, якщо ви не хочете провести вечір, дивлячись на тисячу рядків виводу? Тут допомагає просте правило: не все потрібно аналізувати однаково глибоко. Спочатку відокремте важливі вузли від фонових шумів.
Для міграції CashFlow Dashboard я б подумки ділив залежності на три шари. Платформний: Java, Spring Boot, Gradle, Spring Security, JPA, драйвер БД. Інфраструктурний: JSON, логування, міграції схеми, серіалізація, інтеграційні клієнти. Тестовий і службовий: JUnit, Mockito, test starters, плагіни перевірки. Новачки часто дивляться лише на runtime, забуваючи, що тести й збирання також мають пережити міграцію, — а потім застосунок «майже збереться», а CI завалиться.
Щоб не читати граф у сирому вигляді, корисно зберігати evidence окремо: заведiть каталог evidence/ і складайте туди вивід залежностей. Тоді Claude Code, ви й будь-який рецензент дивитеся в один файл, а не переказуєте одне одному враження.
./gradlew dependencies > evidence/dependencies.txt
./gradlew dependencyInsight --dependency spring-security-config > evidence/security.txt
Цінність тут не команда, а звичка: спочатку зафіксувати evidence, потім інтерпретувати. З Claude Code це окуповується одразу — ви даєте йому конкретний evidence/dependencies.txt, а не «ну там, здається, багато залежностей, подивіться».
Ще один важливий момент для початківців: не кожен рядок графа зобов’язаний стати рядком матриці — до матриці потрапляє лише той, що реально впливає на міграційне рішення. Умовний commons-lang3 живе в проєкті без пригод. А spring-security-config, jakarta.persistence, hibernate-core, postgresql driver, Gradle wrapper і Java toolchain майже напевно заслуговують окремого рядка.
3. Sequence constraints — це частина сумісності
Дуже спокусливо думати, що compatibility matrix — це просто список: компонент, версія, статус, ризик. Але без зафіксованого порядку ви отримаєте красиву таблицю, по якій усе одно не можна безпечно рухатися. У міграції порядок важливий майже так само, як і самі версії, а інколи важливіший: технічно сумісні компоненти можуть вимагати суворої послідовності кроків.
На CashFlow Dashboard це видно особливо добре. Spring Boot 3.x вимагає новішу Java, новий Gradle wrapper — інший toolchain, а перехід із javax.* на jakarta.* безглуздо вносити, поки збирання живе в старому середовищі й не може це перевірити. «Що оновлювати» і «в якому порядку» — це одне завдання на двох рівнях.
Невелика схема допомагає це побачити:
flowchart LR
A[Поточний стан міграції] --> B[Граф залежностей]
C[Офіційна документація / примітки до релізів] --> D[Статус і обмеження]
B --> D
D --> E[COMPATIBILITY_MATRIX.md]
Всередині Status and constraints якраз і живуть sequence constraints. Наприклад, у вас може бути така логіка:
| Компонент | Поточна | Цільова | Важливе обмеження порядку |
|---|---|---|---|
| Java | |
|
Має бути оновлена перед переходом на Boot 3.x |
| Gradle | |
|
Потрібен проміжний крок до нового Boot plugin |
| Spring Boot | |
|
Йде після Java та оновлення частини build-середовища |
Це не означає, що для кожного проєкту порядок буде однаковим, але для кожного проєкту він має бути явним. Якщо sequence constraint не записано, воно все одно існує — просто в голові автора. А все, що живе лише в голові автора, під час міграції зазвичай потім стає багом, якого «чомусь ніхто не очікував».
У матриці sequence constraint можна зберігати прямо в рядку компонента. Коротко і практично:
## Компонент
Gradle
## Поточна версія
7.6.4
## Цільова версія
8.x
## Статус сумісності
requires intermediate version
## Обмеження послідовності
Оновити до переходу на Boot 3.x і перевірити toolchain під Java 21
Такий запис здається надмірним рівно до того моменту, поки хтось не вирішить «одразу спробувати Boot 3, а Gradle потім». Після цього надмірність різко перетворюється на рятівне коло.
4. Словник статусів сумісності
Найчастіша проблема поганих migration-артефактів — розпливчаста мова. «Треба перевірити», «нібито сумісно», «може знадобитися адаптація», «подивимося в процесі». Усе це звучить ввічливо, але не допомагає приймати інженерні рішення. Compatibility matrix добра лише тоді, коли в ній є фіксований словник статусів. Інакше в кожного автора буде свій діалект, а в рецензента — головний біль.
І тут корисно пам’ятати різницю між двома шарами. confirmed / assumption / unknown живуть у research notes і показують, наскільки ми впевнені у висновку. Матриця відповідає на інше запитання: що з цим робити з погляду міграції. Тому незакритий unknown не стає магічно рішенням — зазвичай він потрапляє сюди як needs manual validation, а іноді доживає до Open risks у MIGRATION_PLAN.md.
Для цього рівня корисно триматися одного набору статусів:
| Статус | Що означає на практиці |
|---|---|
|
Оновлення виглядає прямим і не потребує помітних змін коду |
|
Не можна стрибнути одразу; потрібен проміжний крок |
|
Наразі перехід заблоковано несумісністю або відсутністю умов |
|
Компонент простіше замінити, ніж тягнути далі |
|
Код проєкту має бути адаптований під нову версію |
|
Потрібні зміни конфігурації, властивостей, налаштувань збирання |
|
За кодом і docs не можна чесно підтвердити сумісність без ручної перевірки |
Для початківців особливо корисний останній статус. Він дуже дисциплінує. Замість того щоб вдавати впевненість, коли evidence бракує, ви чесно пишете needs manual validation. Це не слабкість, а ознака нормальної інженерної роботи. Слабкість — написати safe update, бо так спокійніше виглядає таблиця.
На практиці один компонент іноді потребує двох позначок одразу. Наприклад, Spring Boot 2.7 → 3.x для CashFlow Dashboard майже напевно означатиме і needs code changes, і needs config changes. У такому разі краще вибрати один основний статус, а другий явно описати в Required action. Але якщо вашій команді зручніше писати подвійний статус, на кшталт needs code changes + needs config changes, це теж допустимо — головне, щоб правило було єдиним по всій матриці.
Ось хороший приклад рядка, який не ховається за туманом формулювань:
## Компонент
Spring Boot
## Поточна версія
2.7.18
## Цільова версія
3.x
## Статус сумісності
needs code changes + needs config changes
За таким рядком уже зрозуміло, що це не «оновили і забули». А далі рецензент іде в Required action і Evidence, де бачить уже конкретику.
5. Читабельний COMPATIBILITY_MATRIX.md
Хороша матриця сумісності — це не величезна Excel-таблиця з пекла і не роман у дванадцяти томах. Це повторюваний, читабельний артефакт, де в кожного рядка один і той самий каркас. Важлива не форма заради форми, а те, що по ній легко пройти поглядом і швидко побачити: де безпечно, де ризик, де blocker, де недостатньо evidence.
Для CashFlow Dashboard я б рекомендував такий шаблон рядка:
## Компонент
...
## Поточна версія
...
## Цільова версія
...
## Статус сумісності
...
## Ризик
...
## Потрібна дія
...
## Обмеження послідовності
...
## Докази
...
Зверніть увагу на поле Evidence. Саме воно відрізняє матрицю від набору розумних думок. В Evidence мають лежати посилання на реальні джерела: build.gradle, gradle-wrapper.properties, вивід dependencies, конкретний клас у коді, розділ migration guide, release notes. Якщо evidence немає, у вас не рядок матриці, а заготовка для обговорення.
Нижче — три короткі приклади, які добре показують різницю між типами рядків.
## Компонент
Spring Boot
## Поточна версія
2.7.18
## Цільова версія
3.x
## Статус сумісності
needs code changes
## Докази
build.gradle; migration guide: Jakarta EE 9
Тут рядок одразу говорить: оновлення впиратиметься не лише у версію, а й у код.
## Компонент
Gradle
## Поточна версія
7.6.4
## Цільова версія
8.x
## Статус сумісності
requires intermediate version
## Докази
gradle-wrapper.properties; Gradle release notes
А тут уже видно, що не можна перестрибнути крок «бо хочеться швидше».
## Компонент
Custom Hibernate UserType
## Поточна версія
legacy implementation
## Цільова версія
jakarta-compatible implementation
## Статус сумісності
needs manual validation
## Докази
src/main/java/.../MoneyUserType.java; docs insufficient
Це чудовий приклад чесного рядка. Код є, проблема видна, але офіційне джерело не дає гарантії, що все переживе міграцію без ручної перевірки. Отже, так і пишемо.
Ще одна корисна звичка: включати до матриці не лише бібліотеки, а й системні компоненти середовища. Java — це компонент. Gradle wrapper — це компонент. Spring Boot plugin — це компонент. PostgreSQL driver — це компонент. Іноді навіть тестовий стек — окремий компонент. Бо міграція ламається не лише в runtime-коді, а в будь-якій точці, де сходяться версія, toolchain і ваша реальна кодова база.
6. Claude Code як аналітик, а не ворожка
Коли у вас уже є current-state inventory, evidence-файли та нотатки з official docs, Claude Code стає дуже корисним помічником. Але саме помічником-аналітиком, а не автоматичним мігратором. Його завдання тут — читати фіксовані входи, допомагати групувати залежності, пропонувати рядки матриці й показувати, де evidence бракує. Не більше. Щойно він починає в дусі «ну зазвичай тут усе сумісно», час повертати розмову на землю.
Хороший запит до Claude Code в цій темі звучить приблизно так:
Побудуйте рядки compatibility matrix для CashFlow Dashboard.
Використовуйте лише:
- build.gradle
- gradle-wrapper.properties
- evidence/dependencies.txt
- migration research notes
Для кожного рядка поверніть:
component, current, target, status, risk, sequence constraint, evidence.
Якщо підтвердження бракує, ставте needs manual validation.
Нічого не оновлюйте і не запускайте.
Зверніть увагу: тут є три важливі обмеження. По-перше, ви задаєте фіксовані входи. По-друге, ви задаєте фіксований словник полів. По-третє, ви прямо забороняєте execution. Це сильно знижує шанс, що Claude піде в жанр «зараз я вам ще й пів проєкту оновлю, раз уже відкрив термінал».
При цьому фінальне рішення по кожному рядку все одно залишається за вами. Claude може допомогти швидко помітити, що spring-boot-starter-security тягне стару гілку security-конфігурації, або що кастомний MoneyUserType не має явної сумісності з jakarta-світом. Але статуси blocked, needs replacement і навіть safe update має затверджувати людина, яка прочитала evidence і розуміє ризик для системи.
Корисна практична перевірка дуже проста. Відкрийте будь-який рядок своєї матриці й спробуйте вголос відповісти на три запитання: що це за компонент, чому в нього такий статус і на яке джерело ви спираєтеся. Якщо на одному із запитань ви починаєте говорити щось на кшталт «ну це Claude припустив», рядок ще не готовий. А якщо кожен рядок читається так само впевнено, як коментар до звичайного PR, значить COMPATIBILITY_MATRIX.md справді виконує свою роботу: показує не лише список залежностей, а реальну карту ризику та сумісності для міграції CashFlow Dashboard.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ