При работе с базой данных миграция схемы является одной из важных задач, которую нам часто приходится выполнять на протяжении всего жизненного цикла приложения, чтобы адаптироваться к новым бизнес-требованиям.
В этой лекции мы узнаем, как написать и запустить миграцию схемы базы данных в Golang, используя библиотеку golang-migrate.
- Ссылка на плейлист с видео лекциями на Youtube
- И на Github репозиторий
Golang-migrate работает со многими различными движками баз данных, такими как Postgres, Mysql, Mongo, Cockroach и т. д.
Давайте откроем эту CLI документацию, чтобы узнать, как его установить. У меня Mac, поэтому я буду использовать Homebrew.
brew install golang-migrate
Golang-migrate позволяет нам использовать несколько команд:
- Первая —
create
, которую мы можем использовать для создания новых файлов миграции. - Вторая — это
goto
, которая осуществит миграцию на определенную версию. - Команды
up
илиdown
осуществляют все или N миграций вперёд или назад.
Существуют ещё несколько команд, но чаще всего мы будем работать с create
, up
и down
.
После того как golang-migrate успешно установлен, мы можем запустить команду
migrate -help
, чтобы прочитать его справочное руководство.
Итак, теперь я создам новую папку для нашего проекта Simple Bank, а внутри
неё - новую папку db/migration
для хранения всех наших файлов миграции.
cd ~/Projects/techschool
mkdir simple_bank
cd simple_bank
mkdir -p db/migration
Затем давайте создадим первый файл миграции, чтобы инициализировать схему базы данных нашего приложения Simple Bank.
Начните ввод в терминале с migrate create
. Затем укажите расширение файла sql
и каталог для его хранения — db/migration
.
migrate create -ext sql -dir db/migration -seq init_schema
Мы используем флаг -seq
для создания последовательного номера версии файла
миграции. И, наконец, укажите название миграции, которое в данном случае равно
init_schema
.
Как видно на рисунке, для нас было сгенерировано 2 файла миграции. Оба они имеют
версию 1 в префиксе имени файла, но их суффиксы разные: у одного файла up
, а
другой — down
. Почему?
Причина в том, что это лучшая практика при написании миграции базы данных. Скрипт
up
запускается для применения изменений к схеме БД, а скрипт down
запускается,
если мы хотим отменить изменения, сделанные скриптом up
.
Поэтому когда мы запускаем команду migrate up
, файлы скрипта up
внутри папки
db/migration
будут запускаться последовательно по возрастанию их версии префикса.
Наоборот, когда мы запускаем команду migrate down
, файлы скрипта down
внутри
папки db/migration будут запускаться последовательно в порядке, обратном их
версии префикса.
Хорошо, теперь давайте откроем файл simple_bank.sql
, который мы создали в
предыдущих лекциях. Я скопирую все содержимое этого файла и вставлю его в файл
init_schema.up.sql
.
CREATE TABLE "accounts" (
"id" bigserial PRIMARY KEY,
"owner" varchar NOT NULL,
"balance" bigint NOT NULL,
"currency" varchar NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT (now())
);
CREATE TABLE "entries" (
"id" bigserial PRIMARY KEY,
"account_id" bigint NOT NULL,
"amount" bigint NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT (now())
);
CREATE TABLE "transfers" (
"id" bigserial PRIMARY KEY,
"from_account_id" bigint NOT NULL,
"to_account_id" bigint NOT NULL,
"amount" bigint NOT NULL,
"created_at" timestamptz NOT NULL DEFAULT (now())
);
ALTER TABLE "entries" ADD FOREIGN KEY ("account_id") REFERENCES "accounts" ("id");
ALTER TABLE "transfers" ADD FOREIGN KEY ("from_account_id") REFERENCES "accounts" ("id");
ALTER TABLE "transfers" ADD FOREIGN KEY ("to_account_id") REFERENCES "accounts" ("id");
CREATE INDEX ON "accounts" ("owner");
CREATE INDEX ON "entries" ("account_id");
CREATE INDEX ON "transfers" ("from_account_id");
CREATE INDEX ON "transfers" ("to_account_id");
CREATE INDEX ON "transfers" ("from_account_id", "to_account_id");
COMMENT ON COLUMN "entries"."amount" IS 'can be negative or positive';
COMMENT ON COLUMN "transfers"."amount" IS 'must be positive';
Для файла init_schema.down.sql
мы должны отменить изменения, внесенные
сценарием up
. В нашем случае скрипт up
создает 3 таблицы: accounts
, transfers
и entries
. Таким образом, скрипт down
должен удалить их все. Для этого мы
воспользуемся командой DROP TABLE
.
DROP TABLE IF EXISTS entries;
DROP TABLE IF EXISTS transfers;
DROP TABLE IF EXISTS accounts;
Здесь мы сначала удаляем таблицы entries
и transfers
, а потом accounts
, из-за
наличия в них внешних ключей, которые ссылаются на записи в таблице accounts
.
Итак, наши скрипты для миграций готовы. Попробуем их запустить.
Но перед этим мы должны проверить, работает ли еще наш контейнер postgres
:
docker ps
Кстати, я покажу вам еще несколько docker команд для работы с контейнерами. Если
мы хотим остановить запущенный контейнер, мы используем docker stop
с именем
или идентификатором контейнера.
docker stop postgres12
После этого, если мы запустим docker ps
, мы больше не увидим контейнер postgres
в списке, потому что он не запущен. Чтобы вывести список всех контейнеров,
независимо от их текущего состояния, выполните команду:
docker ps -a
Теперь мы видим наш контейнер postgres
со статусом exited
.
Чтобы снова запустить его, нам просто нужно выполнить команду: docker start
и
передать ей имя или идентификатор контейнера.
docker start postgres12
После этого контейнер postgres12
снова запущен и готов к работе.
Мы можем получить доступ к его командной оболочке с помощью команды docker exec
.
Поскольку мы используем образ postgres alpine, у нас нет оболочки /bin/bash
,
как в Ubuntu, поэтому вместо нее мы используем оболочку /bin/sh
:
docker exec -it postgres12 /bin/sh
Внутри оболочки у нас есть доступ ко всем стандартным командам Linux.
И поскольку это контейнер postgres, он также предоставляет нам некоторые CLI команды для взаимодействия с сервером postgres непосредственно из оболочки.
Мы должны создать базу данных, прежде чем мы сможем запустить первую миграцию.
Итак, давайте выполним команду createdb
внутри оболочки контейнера postgres,
чтобы создать новую базу данных для нашего приложения Simple Bank:
createdb --username=root --owner=root simple_bank
- Мы используем параметр
--username
, чтобы указать, что мы подключаемся как пользователь root. - И параметр
--owner
, чтобы указать, что база данных, которую мы собираемся создать, также будет принадлежать пользователюroot
. - Последний аргумент — это название базы данных:
simple_bank
.
Отлично, база данных создана. Мы можем получить доступ к ней через консоль с
помощью команды psql
.
Мы также можем удалить базу данных с помощью команды dropdb
и передав
название базы данных.
dropdb simple_bank
Мы используем команду exit
, чтобы выйти из командной оболочки контейнера.
Извне контейнера мы также можем запустить createdb
напрямую с помощью команды
docker exec
.
docker exec -it postgres12 createdb --username=root --owner=root simple_bank
И получить доступ к консоли базы данных, минуя командную оболочку контейнера.
docker exec -it postgres12 psql -U root simple_bank
Итак, теперь я создам Makefile в нашем проекте, затем добавлю команду createdb
для создания базы данных Simple Bank и команду dropdb
для ее удаления.
При работе в команде эти команды будут полезны вашим коллегам, чтобы им было проще настроить проект на своей локальной машине для разработки.
Давайте также добавим в Makefile команду, которую мы использовали для запуска контейнера postgres в предыдущей лекции.
postgres:
docker run --name postgres12 -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres:12-alpine
createdb:
docker exec -it postgres12 createdb --username=root --owner=root simple_bank
dropdb:
docker exec -it postgres12 dropdb simple_bank
.PHONY: postgres createdb dropdb
Хорошо, теперь давайте остановим текущий контейнер postgres.
docker stop postgres12
Контейнер остановлен. Я полностью удалю его с помощью команды docker rm
.
docker rm postgres12
Теперь когда мы выполним команду
make postgres
Стартует новый контейнер postgres. И мы можем запустить
make createdb
чтобы создать базу данных simple_bank
.
ОК, база данных создана. Давайте подключимся к нему с помощью TablePlus.
Соединение, которое мы создали в предыдущей лекции, подключит нас к базе данных
root. Мы можем щелкнуть на значок базы данных, чтобы открыть нашу новую базу
данных simple_bank
(смотри рисунок).
Хорошо, теперь вы видите здесь 2 базы данных: root
и simple_bank
. На данный
момент база данных simple_bank
пуста. Поэтому вернемся в терминал и запустим
первую миграцию.
Начнём написание команды со слова migrate
. Затем воспользуемся параметром
-path
, чтобы указать папку, содержащую наши файлы миграции, то есть db/migration
.
migrate -path db/migration -database "postgresql://root:secret@localhost:5432/simple_bank" -verbose up
Параметр -database
используется для указания URL-адреса сервера базы данных.
- Мы используем postgres, поэтому название драйвера —
postgresql
. - Имя пользователя -
root
. - Пароль -
secret
. - Адрес
localhost
, порт5432
. - И имя базы данных —
simple_bank
.
Мы используем параметр -verbose
, чтобы команда migrate
выводила на экран все
служебные сообщения.
И, наконец, мы используем аргумент up
, чтобы указать команде migrate
, что нужно
запустить команду migrate up
.
Ой, мы получили ошибку: SSL is not enabled on the server (на сервере не включен SSL). Она возникла потому, что наш контейнер postgres не включает SSL по умолчанию.
Поэтому мы должны добавить параметр sslmode=disable к URL-адресу базы данных. Теперь запустите такую команду:
migrate -path db/migration -database "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable" -verbose up
И миграция успешно прошла!
Если мы обновим базу данных simple_bank
в TablePlus, то теперь увидим 4 таблицы:
accounts
, entries
, transfers
и schema_migrations
.
В таблице schema_migrations
хранится последняя примененная версия миграции, в
нашем случае это версия 1, поскольку мы запустили только один файл миграции.
Столбец dirty
сообщает нам, была ли последняя миграция удачной или нет. Если
миграция не прошла, мы должны вручную исправить проблемы, чтобы восстановить корректное
состояние базы данных, прежде чем пытаться запускать любые другие миграции.
Итак, теперь я добавлю команды migrate up
и migrate down
в Makefile:
postgres:
docker run --name postgres12 -p 5432:5432 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres:12-alpine
createdb:
docker exec -it postgres12 createdb --username=root --owner=root simple_bank
dropdb:
docker exec -it postgres12 dropdb simple_bank
migrateup:
migrate -path db/migration -database "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable" -verbose up
migratedown:
migrate -path db/migration -database "postgresql://root:secret@localhost:5432/simple_bank?sslmode=disable" -verbose down
.PHONY: postgres createdb dropdb migrateup migratedown
Теперь давайте попробуем выполнить их в терминале! Сначала я запущу:
make migratedown
Вернитесь в TablePlus и обновите страницу.
Все таблицы исчезли, кроме schema_migrations
.
Хорошо, теперь запустим:
make migrateup
Затем обновите TablePlus.
Все таблицы опять на месте. Прекрасно!
На этом завершим сегодняшнюю лекцию о миграции баз данных. Спасибо за время, потраченное на чтение, и до встречи на следующей лекции!