Хотите заказать веб-сайт? Связаться с нами

Миграция базы данных: создание и запуск

Проект готов, агент настроен, процесс выполнения упражнений отлажен. Но чтобы приложение начало приносить пользу, ему нужны данные, а данным — структура. В этом уроке подготовим базу данных: разберём, что такое миграции, создадим первую миграцию, запустим её и проверим, что структура данных на месте.

Миграция базы данных: создание и запуск

Зачем проекту база данных

До сих пор наш учебный проект работал с данными «в памяти»: программа стартует, что-то считает и завершается. Для реального приложения этого мало — данные нужно хранить между запусками: пользователей, заказы, настройки. Роль такого хранилища выполняет база данных.

База данных — это отдельная программа, которая надёжно хранит данные и умеет быстро их искать. Проект общается с ней на языке SQL — специальном языке запросов. В курсе мы используем SQLite — самую простую базу для обучения: она хранится одним файлом, не требует установки сервера и идеально подходит для учебного проекта.

  1. Хранение — данные переживают перезапуск приложения и не теряются.
  2. Поиск — база быстро находит нужные записи даже среди миллионов строк.
  3. Целостность — база не даёт сохранить противоречивые данные, например два пользователя с одинаковым email.
  4. Масштаб — структуру данных можно развивать, не переписывая приложение целиком.

Но у базы данных есть важная особенность: структуру (таблицы, колонки, связи) нужно создавать и менять управляемо. Именно для этого существуют миграции.

Что такое миграции

Миграция — это файл с изменениями структуры базы данных, оформленный как шаг в истории. Каждая миграция описывает одно изменение: «создать таблицу users», «добавить колонку price», «удалить таблицу logs». Все миграции складываются в упорядоченный список, и база применяет их по очереди.

Зачем такая формальность? Представьте, что структуру базы меняют «вручную» — открыли консоль, выполнили пару команд, забыли. Через неделю никто, включая вас и агента, не сможет сказать, из чего на самом деле состоит база. Миграции решают эту проблему: структура становится кодом, который лежит в репозитории, версионируется и воспроизводится.

  1. История — каждая миграция хранит, что и когда менялось в структуре.
  2. Воспроизводимость — на любой машине база собирается одинаково, одной командой.
  3. Откат — неверное изменение можно отменить, откатив миграцию.
  4. Проверяемость — изменения структуры проходят тот же процесс, что и код: diff, проверка, коммит.

Миграции превращают «что-то сделали с базой» в «вот список изменений, каждое можно посмотреть, применить и откатить». Для работы с агентом это особенно ценно: агент читает миграции, как код, и понимает состояние структуры данных без лишних расспросов.

Структура миграции

Миграция — это не просто SQL-запрос, а парный набор изменений. Хорошая миграция умеет двигаться в обе стороны: применять изменение и отменять его. Поэтому каждая миграция состоит из двух частей.

Up — применяем изменение

Часть up описывает, что нужно сделать с базой: создать таблицу, добавить колонку, изменить тип. Это прямое действие, которое приводит структуру базы к новому состоянию.

Down — отменяем изменение

Часть down — это «отмена» миграции: что нужно сделать, чтобы вернуть базу в предыдущее состояние. Если миграция создала таблицу, отмена — удалить её; если добавила колонку, отмена — удалить колонку.

Зачем нужна часть down, если мы собираемся двигаться только вперёд? Потому что ошибки случаются: миграция применилась, а данные в ней оказались не те, что нужны. Часть down позволяет аккуратно откатиться на шаг назад, исправить миграцию и применить заново. Миграция без down — это односторонняя дверь без ручки с обратной стороны.

Инструмент для миграций

Для запуска миграций нужен инструмент — программа, которая знает, какие миграции уже применены, а какие ещё нет. В экосистеме Node.js таких инструментов несколько: node-pg-migrate, dbmate, knex и другие. Принцип у всех одинаковый, поэтому в уроке мы используем простой подход: папка migrations/ с SQL-файлами и скрипт запуска в package.json.

Начать можно вообще без сторонних библиотек: SQLite умеет выполнять SQL из файла, и для учебного проекта этого достаточно. Добавим в package.json два скрипта: один применяет миграции, второй откатывает последнюю.

{
  "scripts": {
    "migrate:up": "node scripts/migrate.js up",
    "migrate:down": "node scripts/migrate.js down"
  }
}

Скрипт scripts/migrate.js читает файлы из папки migrations/ в порядке номеров, применяет ещё не применённые (команда up) или откатывает последнюю (команда down). Реализацию можно поручить агенту — это первое упражнение урока, а критерий приёмки мы сформулируем чуть ниже.

Не храните состояние применённых миграций «в голове». Инструмент миграций ведёт служебную таблицу schema_migrations, где записано, какие миграции уже применены. Если такой таблицы нет — агент может и должен её создать: без неё невозможно понять, что уже сделано с базой.

Первая миграция

Создадим первую миграцию — таблицу пользователей. Файл называется по правилу «номер_название.sql», например 001_create_users.sql. Номер гарантирует порядок применения: миграции выполняются строго по возрастанию.

migrations/
└── 001_create_users.sql

Внутри файла — две части: up и down. В примере используется простой синтаксис с комментариями-маркерами, который легко читать и агенту, и человеку.

-- up
CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  email TEXT NOT NULL UNIQUE,
  name TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

-- down
DROP TABLE users;

Разберём, что здесь происходит. Таблица users получает четыре колонки: id — уникальный номер записи, email — адрес, который обязан быть и не может повторяться, name — имя пользователя, created_at — дата создания, которая проставляется автоматически. Часть down просто удаляет таблицу целиком.

Обратите внимание на ключевое слово UNIQUE: это пример того, как база сама защищает целостность данных. Приложение может попытаться сохранить двух пользователей с одним email — база откажет. Такую защиту лучше закладывать в структуру сразу, а не проверять в коде.

Запуск миграций

Когда файл миграции готов, применяем его. Команда зависит от выбранного инструмента; в нашем случае это скрипт из package.json.

npm run migrate:up

Что должно произойти: скрипт найдёт файл 001_create_users.sql, выполнит часть up и запишет отметку о применении. Повторный запуск той же команды не должен ничего ломать: инструмент видит, что миграция уже применена, и пропускает её. Миграции идемпотентны — применяются один раз, сколько бы раз их ни запускали.

Если нужно откатить последнюю миграцию, запускаем команду down. База вернётся в состояние до создания таблицы. Это и есть та самая «ручка с обратной стороны двери».

npm run migrate:down

После применения миграции сразу проверяйте и up, и down: применили, убедились, что таблица есть, откатили, убедились, что таблицы нет, применили снова. Пять минут проверки сейчас уберегут от базы, которую невозможно откатить, когда она понадобится для реальных данных.

Как проверить результат

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

Для SQLite проверить структуру можно командой в терминале. Сначала найдём файл базы — по умолчанию он называется data.db и лежит в корне проекта. Затем выведем список таблиц.

sqlite3 data.db ".tables"

В выводе должны появиться schema_migrations и users. Первая таблица — служебная, её создал инструмент миграций; вторая — наша. Если таблицы users нет, миграция не применилась, и это повод посмотреть на ошибку в выводе скрипта, а не «проверять дальше».

Детальнее посмотреть на структуру таблицы можно командой .schema.

sqlite3 data.db ".schema users"

Критерий приёмки для этого упражнения: команда .tables показывает обе таблицы, а .schema users — четыре колонки с теми же типами, что в миграции. Сопоставьте результат с критерием и закоммитьте шаг.

git add .
git commit -m "Шаг: запущена миграция 001_create_users"

Агент и миграции

Миграции — идеальная задача для делегирования агенту, но с оговорками. Агент отлично справится с рутиной: написать SQL, создать файлы, запустить скрипт, показать результат. Однако решение о структуре данных — ответственность человека: именно вы решаете, какие таблицы и колонки нужны приложению.

Хороший способ делегировать — дать агенту полное задание с критерием приёмки, как мы делали в уроке про упражнения.

В проекте есть папка migrations/ и скрипт scripts/migrate.js.
Создай миграцию 002_create_orders.sql: таблица orders
с колонками id, user_id, total, created_at.
CHECK: user_id ссылается на users.id.
Критерий приёмки: npm run migrate:up применяет обе миграции,
sqlite3 data.db ".tables" показывает таблицу orders.

Обратите внимание: в задании указаны не только «что создать», но и способ проверки. Агент выполняет работу, вы проверяете результат по критерию — ровно тот цикл, который мы отрабатывали в предыдущих уроках. Такой же шаблон запроса подойдёт и для реальных проектов: задача, ограничения, критерий приёмки.

Коммитьте миграцию отдельным коммитом от кода приложения. Миграции меняют структуру базы — это самый рискованный тип изменений, и у него должна быть собственная точка возврата в истории. Один коммит «фича + миграция» в случае отката заставит распутывать оба изменения сразу.

Типичные ошибки

  1. Изменение структуры базы «вручную» в консоли вместо миграции — история теряется.
  2. Миграция без части down — откатить изменение невозможно.
  3. Миграция без служебной таблицы применённых шагов — повторный запуск ломает базу.
  4. Изменение уже применённой миграции вместо создания новой.
  5. Пропуск проверки результата: «миграция прошла» ≠ «таблица создана».
  6. Коммит миграции вместе с кодом фичи в одном коммите.

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

Итоги

База данных проекта готова: мы разобрались, зачем нужны миграции, создали первую миграцию с частями up и down, запустили её и проверили структуру по критерию приёмки. Теперь структура данных — это код: она лежит в репозитории, применяется одной командой и откатывается при необходимости.

В следующем уроке научимся возвращать проект в чистое состояние — сбрасывать базу и файлы к исходной точке, чтобы практиковаться многократно без накопления «грязи».

Глоссарий

  1. База данных — программа для хранения и поиска данных между запусками приложения.
  2. SQL — язык запросов к базе данных.
  3. SQLite — файловая база данных, не требующая установки сервера.
  4. Таблица — структура для хранения однотипных записей в базе.
  5. Колонка — одно поле записи в таблице, с типом и ограничениями.
  6. Миграция — файл с изменением структуры базы, оформленный как шаг истории.
  7. Up — часть миграции, применяющая изменение к базе.
  8. Down — часть миграции, отменяющая изменение.
  9. schema_migrations — служебная таблица со списком применённых миграций.
  10. Идемпотентность — свойство операции давать одинаковый результат при повторном запуске.
  11. UNIQUE — ограничение колонки, запрещающее повторяющиеся значения.
  12. Откат — возврат базы к предыдущему состоянию через миграцию down.
  13. sqlite3 — консольный клиент для работы с базой SQLite.

Теги: