JavaRush /Курсы /Claude code /Карта типов миграций

Карта типов миграций

Claude code
29 уровень , 3 лекция
Открыта

1. Слово «миграция» слишком общее

Когда вы впервые слышите слово migration, очень легко представить что-то однотипное: подняли версии, поправили пару импортов, прогнали тесты, готово. Проблема в том, что под одним словом прячутся изменения разной природы: одни ломают компиляцию, другие — поведение в рантайме, третьи — только сборку в CI. Поэтому первое взрослое действие в migration execution — назвать тип миграции, а не объявить: «мы обновляем проект».

До этого места у нас был один pilot: migration/boot3-pilot на reports/* с выбранным scope, rollback и validation. Сейчас я не расширяю его до всего сервиса, а раскладываю по типам изменений: где риск и какие датчики на каждом слое. Данные и конфигурацию сюда сознательно не включаю — там другой риск и другой паттерн безопасности.

На практике полезно держать перед глазами вот такую карту:

Семейство миграции Что реально меняется Где чаще всего ломается Первая полезная проверка
single-library одна конкретная зависимость, которую вы обновляете явно локальная сборка, интеграционные тесты точечный diff версии + прогон целевого набора тестов
dependency дерево зависимостей целиком, включая транзитивные конфликты версий, несовместимые плагины, неожиданные классы в classpath сравнение lockfile или графа зависимостей
framework правила платформы, API, автоконфигурация, соглашения компиляция, конфигурация, безопасность, поведение в рантайме чтение release notes + characterization tests
runtime версия языка и среды исполнения CI, контейнер, рефлексия, TLS, поведение JVM matrix-прогон на старой и новой версии
build wrapper, build plugins, pipeline, образ сборки «локально работает, в CI падает» clean build в CI на целевой среде

Эта таблица кажется почти скучной — а это хороший знак: инженерная карта и должна выглядеть чуть скучнее, чем катастрофа. Скучная таблица дешевле бодрого созвона в 23:40 с вопросом «а почему после обновления Java у нас отвалился отчёт, хотя код мы не трогали?».

Для CashFlow Dashboard это особенно полезно, потому что первый большой прыжок в курсе — не одна миграция, а гибрид: мы задеваем framework, runtime и build одновременно, а рядом шевелятся зависимости. Не разложите эту смесь на части — и любой MIGRATION_PLAN.md превратится в литературное произведение без шансов на исполнение.

2. Один цикл для всех типов миграций

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

flowchart LR
    A[Changelog / release notes] --> B[Pilot-срез]
    B --> C[Tests и checks]
    C --> D[Маленький diff]
    D --> E[Review и решение]

Ключевое здесь — сначала читать release notes, а не сразу редактировать build.gradle с фразой «ну сейчас всё само подскажет». Claude Code в этой фазе полезен, но в очень конкретной роли: читать документацию, сопоставлять breaking changes с файлами pilot-среза, подсказывать, где поможет компилятор, а где спасут тесты или smoke checks. Хороший запрос выглядит так:

Изучи release notes Spring Boot 3.x и наш pilot-срез `reports/*`.
Код не меняй.
Верни:
1) какие breaking changes относятся к этому срезу;
2) что поймает компилятор;
3) что поймают только тесты или runtime;
4) с какого файла безопаснее начать pilot.

Обратите внимание на тон запроса: не «мигрируй модуль», а «прочитай, сопоставь, объясни, где риск». Это verification-first: модель помогает увидеть карту, а не бросается перестраивать город.

3. Миграции single-library и dependency

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

Для начинающего разработчика удобно разделить два близких понятия. Single-library migration — вы явно меняете одну зависимость: версию SDK, драйвера, клиента, starter-а (не путайте со сменой package или namespace). Dependency migration — когда из-за этого меняется всё дерево. В Java это заметно из-за BOM, плагинов и библиотек, связанных через classpath; в JavaScript аналог — peer dependencies.

Вот почему lockfile — не бюрократия, а улика: показывает, что реально поменялось, а не что вы думали, что поменяли.

./gradlew dependencies > build/deps-before.txt
./gradlew --write-locks
./gradlew clean test --tests 'reports.*'
git diff gradle.lockfile          # смотрим, кто реально изменился
./gradlew dependencyInsight --dependency jackson-databind

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

В CashFlow Dashboard такой подход особенно важен рядом с финансовой логикой. Тронете библиотеку в payments или billing — цена ошибки высока, поэтому первый pilot-срез не начинается там. Берите reports или другой read-only участок: от сложных зон вы не отказываетесь навсегда, просто не делаете из них полигон для первых падений.

И ещё один важный момент — у single-library/dependency migration очень распространённый анти-паттерн: «раз уж залезли, обновим заодно всё устаревшее». Звучит хозяйственно, но разрушает доказуемость. Обновили восемнадцать зависимостей и два дня ищете, чей сюрприз пришёл в сборку, — это не миграция, а археология после взрыва.

4. Framework migration: меняются правила платформы

С framework migration всё хитрее: здесь вы меняете не версию зависимости, а правила игры всей платформы — соглашения, автоконфигурацию, жизненный цикл компонентов, интеграцию с безопасностью и валидацией. Поэтому major-upgrade почти всегда больнее, чем выглядит в diff.

В нашем проекте это видно наглядно: Spring Boot 2.7 → Boot 3.x — классическая framework migration. В ней есть dependency- и runtime-слой, но главная проблема — изменились базовые контракты платформы. Самый заметный и мемный пример — переход javax.* → jakarta.*.

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.validation.Valid;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.validation.Valid;

Компилятор такое ловит охотно, и это даже приятно — часть работы делается шумно и честно. Но framework migration не заканчивается на импортах. Куда неприятнее то, что не падает при компиляции: правила Spring Security, actuator, наблюдаемость, автоконфигурация. Полезно смотреть на framework migration так — кто что ловит:

Что меняется Кто обычно ловит проблему
javaxjakarta в коде компилятор
изменения в security-конфигурации интеграционные тесты
actuator / observability / автоконфигурация runtime и CI
поведение web-слоя и сериализации API tests и smoke checks

Именно поэтому release notes для framework migration не факультативны: их не читают только те, кто любит узнавать про breaking changes из production-логов. Если у вас есть characterization tests из предыдущих тем, драматизма меньше: вы не верите, что «Boot 3 тоже должен работать», а сравниваете поведение pilot-среза до и после. Это превращает миграцию из ритуала в инженерную процедуру.

В CashFlow Dashboard первый framework-pilot логично начинать не с billing, payments или auth, а со спокойного отчётного контроллера, где цена ошибки ниже.

5. Runtime migration: тот же код, новая платформа

Runtime migration часто недооценивают, потому что кажется: раз код компилируется на новой версии языка — вроде всё хорошо. К сожалению, среда исполнения гораздо коварнее. Вы меняете поведение виртуальной машины, базовые библиотеки, TLS, рефлексию, контейнерный образ, иногда даже то, как запускаются тесты.

В курсовом проекте Java 8 → Java 21 — не приложение к framework migration, а полноценная runtime migration. Boot 3 требует более новую Java, поэтому линии идут вместе, но разделять их в голове полезно. Иначе любая проблема выглядит как «Boot 3 что-то сломал», хотя на деле CI просто собирает проект на старом JDK.

Самый дешёвый способ не спорить о среде исполнения — гонять старый и новый runtime на одном наборе проверок. Если код поддерживает обе версии, удобен matrix в CI; но для pilot-а Boot 2.7 / Java 8 → Boot 3.x / Java 21 корректнее сравнивать baseline branch на JDK 8 и migration branch на JDK 21 — Boot 3 не обязан запускаться на Java 8.

# Такой matrix-подход подходит, когда один и тот же код поддерживает обе JDK.
strategy:
  matrix:
    java: [ '8', '21' ]
steps:
  - uses: actions/setup-java@v4
    with:
      java-version: ${{ matrix.java }}
  - run: ./gradlew test --tests 'reports.*'

Смысл этого фрагмента не в красоте YAML, а в дисциплине: воспроизводимые проверки на двух рантаймах покажут, где поведение расходится. Если baseline стабилен, а migration branch сыпется — копать в runtime-слое, а не обвинять вслепую весь framework upgrade.

У runtime migration есть ещё одна неприятная особенность: часть проблем не видна локально, когда среда разработчика отличается от CI. Отсюда правило: не «на ноутбуке зелёное», а «в пайплайне зелёное». Только это evidence — остальное оптимизм.

6. Build migration: ломается способ сборки

Build migration — тот тип изменений, который чаще всего раздражает сильнее всех, потому что код может быть абсолютно нормальным, а проект всё равно не собирается. И самое обидное, что разработчик в таких случаях любит говорить: «Но я же бизнес-логику не менял». Именно в этом и проблема: ломается не предметная область, а механика доставки — wrapper, плагины, pipeline, образ сборки, иногда шаги генерации кода.

Когда вы меняете build-слой, вы фактически отвечаете на вопрос: «Соберём ли мы тем же способом проект завтра, в CI, на другой машине?». Если ответ зависит от фаз луны — build migration ещё не завершена.

Простейший build-pilot не должен быть героическим — его задача доказать воспроизводимость:

- name: Проверка окружения
  run: |
    java -version
    ./gradlew --version
- name: Полная сборка pilot-среза
  run: ./gradlew clean build

Этот кусок хорош тем, что сразу показывает, какая Java и какой Gradle wrapper реально в пайплайне. Если локально вы на Java 21, а в CI старый рантайм — лучше увидеть это в первых строках лога, чем после двадцати минут падений на непонятных плагинах.

Здесь же прячется ещё одна тонкость: build-пайплайн и сгенерированный конфиг — тоже часть migration-картины. Обновили Boot и Java, а Docker-образ в CI тащит старую среду; или wrapper новый, а plugin для генерации кода несовместим. «Работает локально, падает в CI» — build migration, которую просто не назвали своим именем.

Если сказать совсем просто, build migration проверяет не «умеет ли код жить», а «умеет ли команда снова и снова получать один артефакт из чистого состояния». Для legacy это важнее рефакторинга: без воспроизводимой сборки вы и откат не сделаете.

7. Карта типов миграций для CashFlow Dashboard

Теперь соберём всё в одну картинку на нашем проекте — это полезно не только для понимания, но и как почти готовый фрагмент MIGRATION_PLAN.md. Когда вы называете тип миграции по слоям, план сразу становится внятнее: у каждого слоя появляется своя проверка, свой pilot и свои исключения.

Слой в первом pilot CashFlow Dashboard Что меняем в reports-срезе Чем доказываем корректность Что сознательно не трогаем
framework Spring Boot 2.7 → 3.x, javax → jakarta, часть web/autoconfig компиляция + интеграционные тесты отчётов + smoke на endpoint billing, payments, auth
runtime Java 8 → 21 для того же pilot-среза CI matrix, один и тот же набор тестов на двух JDK глобальные JVM-тонкости всей системы
build wrapper, CI Java version, шаги сборки clean build в CI и повторяемый лог окружения release pipeline целиком
dependency сопутствующие библиотеки, подтянутые переходом lockfile diff + dependencyInsight + targeted tests крупные обновления внешних SDK

Данных и конфигурации здесь сознательно нет: как только вы добавляете schema changes, backup и owner approval начинают жить по другим правилам, а pilot перестаёт быть тем самым дешёвым Boot 3 срезом.

Небольшой фрагмент плана в таком стиле может выглядеть так:

## Карта pilot-среза
- framework: Spring Boot 2.7 → 3.x в `reports/*`
- runtime: Java 8 → 21 для того же среза
- build: обновить wrapper и CI JDK
- excluded: `billing/*`, `payments/*`, schema changes

Это уже звучит как инженерный документ, а не боевой клич. И в этом, собственно, весь смысл сегодняшней лекции. Перестаёте говорить «обновим проект» и начинаете «делаем framework + runtime + build pilot на reports/* с отдельным dependency review» — и миграция становится управляемой. Не лёгкой, не безошибочной — управляемой. В legacy-коде это почти всегда лучшая новость дня.

1
Задача
Claude code, 29 уровень, 3 лекция
Недоступна
Документирование migration type map
Документирование migration type map
1
Задача
Claude code, 29 уровень, 3 лекция
Недоступна
Включение dependency locking для build layer
Включение dependency locking для build layer
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ