Начинал я с Flyway, потому что он проще — кинул SQL-файлы в папку, всё заработало. Но через полгода на проде понадобилось откатить колонку, и я обнаружил, что у Flyway нет встроенного rollback. Пришлось писать SQL-скрипты вручную и молиться, что ничего не упадёт. На следующем проекте мы выбрали Liquibase — и набили другие шишки. Рассказываю, что из этого вышло.
Flyway в Kotlin/Spring Boot проекте
Подключение Flyway к Spring Boot — буквально три строки в build.gradle.kts:
dependencies {
implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-database-postgresql")
runtimeOnly("org.postgresql:postgresql")
}Миграции кладём в src/main/resources/db/migration. Нейминг строгий: V1__create_users_table.sql, V2__add_email_column.sql. Порядок определяется версионным префиксом, и Flyway применит их последовательно.
-- V1__create_users_table.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- V2__add_email_unique.sql
ALTER TABLE users ADD CONSTRAINT uq_users_email UNIQUE (email);В application.yml минимальная конфигурация:
spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
datasource:
url: jdbc:postgresql://localhost:5432/mydb
username: postgres
password: ${DB_PASSWORD}Плюсы Flyway очевидны: всё, что ты пишешь — это чистый SQL. Нет абстракций, нет магии. Открыл файл — видишь, что будет выполнено. Для команды из 3-5 backend-разработчиков, которые уверенно пишут SQL, это идеальный вариант. Но есть нюансы.
Проблема rollback в Flyway
Flyway Community не поддерживает автоматический откат. Да, есть Flyway Teams с командой flyway undo, но это платно. В комьюнити-версии у тебя два пути: либо писать undo-миграции вручную с префиксом U, либо просто писать новые миграции, которые «чинят» предыдущую.
На проде мы однажды добавили колонку phone_number с NOT NULL без дефолта. Миграция упала на таблице с миллионом строк. Откатить нечем. Пришлось руками коннектиться к БД и удалять колонку. После этого мы завели правило: все новые колонки — либо с дефолтом, либо через два шага — сначала nullable, потом fill + alter.
Liquibase: когда абстракция помогает
На втором проекте выбрали Liquibase, потому что у нас было три окружения: PostgreSQL в проде, MySQL на стейдже и H2 для тестов. Написание отдельных SQL-файлов для каждой СУБД превратилось бы в ад.
Подключение в build.gradle.kts:
dependencies {
implementation("org.liquibase:liquibase-core")
}А вот changelog уже в YAML-формате:
databaseChangeLog:
- changeSet:
id: 1
author: artem
changes:
- createTable:
tableName: orders
columns:
- column:
name: id
type: BIGINT
autoIncrement: true
constraints:
primaryKey: true
- column:
name: status
type: VARCHAR(50)
defaultValue: "NEW"
constraints:
nullable: false
rollback:
- dropTable:
tableName: ordersОбратите внимание на блок rollback — он описывает, как откатить изменение. Liquibase может сгенерировать SQL для отката автоматически или выполнить его по команде.
Конфигурация в application.yml:
spring:
liquibase:
enabled: true
change-log: classpath:db/changelog/master.yaml
contexts: devЧто мы поняли на практике
Вот критерии, по которым мы сейчас выбираем:
Выбирайте Flyway, если: команда уверенно пишет SQL, нет необходимости поддерживать несколько СУБД, rollback делается через новые миграции (а не через undo), и вы хотите максимальную простоту. Flyway быстрее стартует, проще в дебаге, и SQL-файлы читает любой разработчик.
Выбирайте Liquibase, если: нужна поддержка нескольких СУБД, требуются автоматические rollback, в команде есть junior-разработчики которым опасно давать прямой SQL, или вы работаете в корпоративной среде с обязательным код-ревью миграций. Liquibase также круто интегрируется с Liquibase Hub для отслеживания статуса миграций.
Лично я сейчас по умолчанию беру Flyway для новых проектов на PostgreSQL. Но если вижу, что проект растёт и будут сложные миграции с data-трансформациями — пересаживаюсь на Liquibase. Перенос с Flyway на Liquibase болезненный, так что лучше определиться в начале.
Антипаттерны, которые мы встречали
Первый — хранение данных в миграциях. Когда кто-то пишет INSERT INTO ... VALUES внутри миграции для «начального наполнения» справочников. Это норма для статических данных, но у нас коллега закоммитил 5000 строк INSERT для каталога товаров. Миграция стала тормозить на каждом деплое. Для seed-данных лучше использовать отдельный механизм — Liquibase contexts или Flyway repeatable migrations.
Второй — изменение уже применённой миграции. Это ломает checksum и Flyway, и Liquibase. Всегда создавайте новую миграцию. Если нужно поправить предыдущую — делайте это через новую, не трогайте старую.
Третий — отсутствие IF EXISTS и IF NOT EXISTS в SQL. Да, Flyway отслеживает, какие миграции применены, но при ручном вмешательстве в БД (а это бывает) отсутствие проверок приводит к ошибкам при повторном запуске.
Пока нет комментариев
Будьте первым, кто оставит комментарий к этой статье