1. Роль seed data в локальной среде
Когда вы поднимаете локальный стенд, особенно через Docker Compose, хочется, чтобы через 30 секунд можно было сделать запрос в API и увидеть не пустоту, а что-то полезное. Иначе вы поднимаете целую PostgreSQL, запускаете миграции, ждёте старт приложения… чтобы получить в ответ: «список пуст». Это как открыть холодильник после долгой готовки и обнаружить там только ледяную полку и чувство вины.
Seed data решает сразу две практические задачи. Во‑первых, оно делает локальную среду “живой”: вы можете сразу проверить GET /api/catalog/items, увидеть несколько элементов и убедиться, что persistence действительно работает. Во‑вторых, seed data становится якорем воспроизводимости: если студент или коллега сделал полный reset окружения, у него снова появится ожидаемое базовое содержимое, а не «каждый раз по-разному, потому что я вчера руками что-то вставлял».
Есть важный нюанс: seed data не значит «запихнуть в базу 10 000 строк и имитировать прод». Для курса по Docker нам нужна небольшая, предсказуемая порция данных, которая помогает наблюдать поведение сервиса, миграций и reset’ов. Чем меньше магии и “случайностей”, тем лучше.
Именно поэтому вообще имеет смысл различать обычный rerun и clean reset. Если после docker compose down -v и нового старта у вас снова появляются те же записи, значит baseline правда живёт в репозитории. Если данные “восстанавливаются” только потому, что кто-то когда-то вставил их руками в локальную БД, это не baseline, а счастливое совпадение.
2. Seed data в миграциях Flyway
У новичков часто возникает соблазн сделать «скрипт для заполнения базы» где-нибудь в scripts/seed.sql и запускать его руками, когда «надо». И это работает ровно до первого дня, когда вы устали, спешите, и вместо seed-скрипта запускаете не тот файл. После этого у вас «вроде бы работает», но у коллеги — нет, а у вас — работает «после трёх танцев с бубном». В учебном проекте мы как раз пытаемся выработать привычку, которая спасает от этого.
В нашем Container-Ready Catalog Service seed data — это не внешняя ручная процедура, а часть того же механизма, что и создание схемы: Flyway migrations. По умолчанию Flyway ищет файлы в classpath:db/migration, то есть в нашем проекте это:
src/main/resources/db
└─ migration
Минимальная «правильная» структура для курса выглядит так:
src/main/resources/db/migration
├─ V1__init.sql
└─ V2__seed_demo_data.sql
Смысл разделения очень простой и жизненный. V1__init.sql отвечает за структуру (таблицы, ограничения), а V2__seed_demo_data.sql — за стартовые записи. Это делает миграции читаемыми и диагностируемыми: если что-то пошло не так, вы по логам быстро понимаете, на каком уровне проблема — на уровне схемы или на уровне данных.
И ещё один важный момент: Flyway хранит историю применённых миграций в самой базе (в таблице вроде flyway_schema_history). Поэтому если база живёт в named volume, то и «память о миграциях» живёт там же. Именно поэтому после docker compose down вы можете поднять стек снова и увидеть, что миграции не применяются заново. А после docker compose down -v — применяются, потому что база «родилась заново».
3. Минимальный seed-файл
Seed data хочется сделать «красивой» и «реалистичной». В этот момент очень легко улететь в сторону мини‑продакшена: сложные описания, десятки статусов, куча связанных таблиц, попытка заселить всё сразу. Но в Docker-курсе наша цель проще: быстро получить данные, которые можно увидеть через API, и которые помогают понять, что БД действительно работает.
Допустим, в V1__init.sql у нас была создана таблица catalog_item (упрощённо, как в предыдущих примерах дня). Тогда seed-миграция может выглядеть так:
-- V2__seed_demo_data.sql
-- Демо-данные: коротко, предсказуемо, без random()/now()
INSERT INTO catalog_item (sku, title, price)
VALUES
-- Стабильные SKU удобны для поиска глазами в логах/ответах API
('BOOK-001', 'Docker Basics', 29.90),
('BOOK-002', 'Spring in Containers', 39.90),
('BOOK-003', 'PostgreSQL for Humans', 24.50);
Заметьте, тут есть несколько негласных «учебных хороших манер». Мы используем стабильные sku, чтобы записи можно было узнавать глазами в логах и ответах API. Мы не вставляем id, потому что он обычно генерируется (BIGSERIAL). Мы не делаем random() и now() просто ради красоты — потому что seed data в учебном проекте должна быть предсказуемой, а не “каждый старт — новая вселенная”.
Если в вашем реальном проекте есть дополнительные поля типа status, created_at, updated_at, то можно сделать это явно, но всё равно коротко. Например:
-- V2__seed_demo_data.sql
-- Если есть обязательные поля (status и т.п.) — заполняем их явно
INSERT INTO catalog_item (sku, title, price, status)
VALUES
('BOOK-001', 'Docker Basics', 29.90, 'ACTIVE'),
('BOOK-002', 'Spring in Containers', 39.90, 'ACTIVE');
Главное — помнить: seed data в рамках курса это инструмент воспроизводимости, а не витрина “как мы умеем писать SQL”.
4. Ручная правка БД как анти-recovery
Если вы уже немного видели SQL, рука сама тянется к подходу: «Ой, миграция упала? Сейчас зайду в базу, быстренько поправлю табличку, и всё». Это как чинить компиляцию Java-проекта тем, что вы вручную правите байткод в .class файле. Теоретически возможно, но в учебной реальности это почти всегда превращается в невоспроизводимую магию.
Проблема даже не в том, что вы «неправильно» поправите. Проблема в том, что вы ломаете источник истины. Источник истины — это репозиторий: миграции, которые лежат в db/migration, и которые любой участник команды должен уметь применить, не зная вашей личной истории ночных приключений в psql.
Когда вы меняете базу руками, вы создаёте рассинхрон. Flyway думает, что схема соответствует определённому набору версий, а реальная база уже «чуть-чуть другая». Потом вы вносите новую миграцию V3, и она падает с ошибкой, которую невозможно понять, глядя только на Git. И вот тут рождаются легенды: «Docker какой-то нестабильный», «Flyway у нас капризный», «PostgreSQL сегодня не в настроении». На самом деле просто кто-то однажды «быстро поправил» базу вручную.
Если хочется коротко, то можно запомнить так:
| Подход | Что происходит в реальности | Чем заканчивается |
|---|---|---|
| Ручная правка БД | Вы чините только свою локальную копию | Через неделю никто не понимает, что у вас за состояние |
| Правка миграции / новая миграция | Вы чините проект, а не базу | Любой может воспроизвести, а reset всегда возвращает baseline |
И да, иногда в работе действительно используют ручные запросы для диагностики, но не как способ “исправить” систему. Диагностика — окей. “Лечение” — только через репозиторий.
5. Recovery workflow без шаманства
Когда контейнер приложения перестал стартовать, очень хочется сделать три вещи: перезапустить всё, потом перезапустить ещё раз, потом добавить sleep 20 и сделать вид, что так и было задумано. Но мы с вами уже достаточно взрослые, чтобы стыдиться этого ровно пять секунд — и перейти к нормальному алгоритму.
Сценарий восстановления локальной среды после broken migration удобно держать в голове как короткую цепочку наблюдаемых шагов. Она начинается не с «удалить всё», а с «понять класс проблемы», а заканчивается не на up, а на проверке API.
flowchart TD
A["Контейнер app не стартует / упал"] --> B["Смотрим логи app"]
B --> C{"Это миграция Flyway?"}
C -->|Нет| D["Ищем wrong-host / wrong-profile / wrong-password"]
C -->|Да| E["Находим версию и файл миграции"]
E --> F["Исправляем SQL в репозитории"]
F --> G{"Нужен clean reset базы?"}
G -->|Нет| H["docker compose up --build"]
G -->|Да| I["docker compose down -v && docker compose up --build"]
H --> J["Проверяем: logs + /actuator/health + /api/catalog/items"]
I --> J
Теперь то же самое человеческим языком.
Сначала вы смотрите логи приложения, а не базы. Именно в логах Spring Boot + Flyway обычно написано, какая миграция упала: версия, имя файла и SQL-ошибка. Потом вы сверяете, что PostgreSQL вообще доступен и healthy (логи базы тоже полезны, но обычно вторичны). Затем вы исправляете причину в репозитории: либо правите новую миграцию, если она ещё не применялась, либо добавляете новый шаг истории, если старая уже успела стать частью применённой схемы.
После этого базовый путь — пересобрать образ приложения и снова поднять стек на том же volume. Для PostgreSQL это нормальный сценарий: неуспешная миграция обычно откатывается транзакцией, поэтому после фикса файла Flyway просто снова пытается применить непройденный шаг. docker compose down -v нужен для другой задачи: когда вы сознательно хотите вернуть БД к чистому baseline, прогнать всю историю с нуля и убедиться, что seed data поднимается без ручных хвостов.
И наконец, “готово” наступает только тогда, когда вы проверили три вещи: логи, health endpoint и один бизнес-endpoint. Без этого вы просто не знаете, что у вас реально восстановилось.
6. Мини-демо: сломали миграцию и восстановились
Представим типичный “контролируемый ужас”: вы добавили новую миграцию V3__add_status.sql, но ошиблись в имени таблицы (лишняя буква, неправильное число — классика жанра).
Например, так:
-- V3__add_status.sql (плохой вариант)
-- Ошибка: таблица называется catalog_item, а не catalog_items
ALTER TABLE catalog_items
ADD COLUMN status VARCHAR(32);
Таблица у нас, допустим, называется catalog_item, а не catalog_items. Что произойдёт? PostgreSQL скажет что-то в духе “relation does not exist”, Flyway поймёт, что миграция упала, и Spring Boot не завершит старт.
В логах приложения это обычно выглядит примерно так:
app-1 | Migration V3__add_status.sql failed
app-1 | ERROR: relation "catalog_items" does not exist
app-1 | Application run failed
Теперь “правильная магия” выглядит так: мы исправляем миграцию в репозитории, а не в базе. Фикс — банальный:
-- V3__add_status.sql (исправленный вариант)
-- Исправление: меняем имя таблицы на реальное
ALTER TABLE catalog_item
ADD COLUMN status VARCHAR(32);
После этого нам нужно, чтобы контейнер приложения получил обновлённый jar, ведь миграции лежат внутри приложения (в src/main/resources, а значит — внутри jar). Поэтому базовый recovery path в Compose выглядит так:
# Пересобираем образ приложения и снова поднимаем стек на том же volume
docker compose up --build
Если же цель другая — не просто пережить конкретную поломку, а сознательно проверить проект на чистой базе, тогда уже уместен clean reset:
# Стираем локальное состояние БД и заново прогоняем всю историю миграций и seed baseline
docker compose down -v
docker compose up --build
Почему сначала уместен именно обычный rerun? Потому что в нашем baseline с PostgreSQL неуспешная миграция обычно откатывается, и после исправления файла Flyway просто повторяет этот шаг. down -v нужен не для “лечения SQL”, а для clean replay: проверить, что проект поднимается с нуля, seed data возвращается автоматически, а локальный drift исчезает.
При этом контейнер PostgreSQL можно вообще не пересобирать: он поднимается из официального образа. А контейнер приложения нужно пересобрать, потому что код и ресурсы (включая SQL миграции) менялись.
7. Критерии успешного восстановления
Есть болезненный, но полезный навык: перестать считать победой момент, когда docker compose up перестал ругаться. Контейнер может быть запущен, процесс может жить, а сервис при этом быть “полумёртвым”: миграции не дошли, схема не совпала, приложение зависло на старте или живёт без нужного профиля.
Для нашего учебного сервиса критерии “всё снова работает” простые и проверяемые.
Во‑первых, в логах app вы должны увидеть, что Flyway применил нужные миграции и приложение дошло до Started ...:
app-1 | Successfully applied 3 migrations
app-1 | Started CatalogApplication
Во‑вторых, Actuator health должен быть UP:
# Проверяем, что приложение действительно healthy
curl -s http://localhost:8080/actuator/health
# Ожидаем: {"status":"UP"}
В‑третьих, бизнес-эндпоинт должен отвечать и, в случае чистого старта, показывать seed data:
# Проверяем бизнес-эндпоинт и наличие стартовых данных (seed)
curl -s http://localhost:8080/api/catalog/items
# Ожидаем, что вернутся элементы, например:
# [{"id":1,"sku":"BOOK-001","title":"Docker Basics","price":29.90}, ...]
Если вы после down -v не видите стартовых данных, это почти всегда означает одно из двух. Либо seed-миграции нет (или она пустая), либо приложение вообще не в том профиле и не смотрит на PostgreSQL (например, запустилось в standalone). И вот это уже нормальная, понятная диагностика, а не “что-то где-то сломалось”.
8. Типичные ошибки при seed data и recovery workflow
Ошибка №1: seed data живёт в голове или в личном файле, а не в миграциях.
Очень часто стартовые данные появляются “случайно”: кто-то однажды вставил пару строк через psql, и дальше все считают, что так и должно быть. До первого полного reset’а. Потом половина команды видит пустую базу и начинает чинить проблему с конца. Seed data должна быть версией в db/migration, иначе это не seed, а легенда.
Ошибка №2: исправлять упавшую миграцию «в базе», а не в репозитории.
Когда миграция упала, хочется быстро “добавить столбец руками” и поехать дальше. Но так вы не исправляете причину — вы прячете её. Через время вы получите вторую ошибку, и она будет выглядеть вообще не связанной с первой. Источник истины — migration-файлы в Git. Локальная база — только результат их применения.
Ошибка №3: всегда лечить любую проблему docker compose down -v.
down -v — это мощная кнопка “стереть память”. Иногда она реально нужна (например, после broken migration). Но если делать так на каждую мелочь, вы постоянно теряете состояние БД и перестаёте понимать, какие проблемы связаны с данными, а какие — с конфигурацией. В учебной и рабочей жизни важно, чтобы reset был осознанным, а не рефлексом.
Ошибка №4: забыть пересобрать образ приложения после изменения миграций.
Миграции лежат в ресурсах приложения. Если вы поменяли SQL-файл, но не пересобрали jar или образ, то контейнер всё равно запускает старую версию. Потом вы смотрите в логи и думаете: “Я же исправил… почему он снова ругается?”. Потому что вы исправили на host-машине, а в контейнере живёт прошлый артефакт. Для Compose это обычно означает запуск с --build.
Ошибка №5: считать восстановление завершённым после docker compose up.
up — это только старт. Восстановление заканчивается, когда вы увидели успешные миграции в логах, получили UP на /actuator/health и сделали хотя бы один запрос к API. Без этого вы просто переносите проблему в будущее: “ну вроде поднялось, но завтра опять упадёт”.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ