Для запуска БД в контейнере выполните:
docker compose up yabinar-3-postgresСоздадим первую миграцию:
docker run --rm \
-v $(realpath ./db/migrations-new):/migrations \
migrate/migrate:v4.18.1 \
create \
-dir /migrations \
-ext .sql \
-seq -digits 5 \
initОпишем первую версию БД. Добавим в 00001_init.up.sql следующий код:
CREATE TABLE positions(
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
title VARCHAR(200) UNIQUE NOT NULL
);
CREATE TABLE employees(
id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
first_name VARCHAR(200) NOT NULL,
last_name VARCHAR(200) NOT NULL,
salary NUMERIC NOT NULL,
position INT NOT NULL REFERENCES positions(id),
CONSTRAINT employees_salary_positive_check CHECK (salary::numeric > 0)
);Попробуем применить миграцию к БД (имя сети можно найти при помощи команды docker network ls):
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
upПодключимся к БД:
psql -h localhost -p 5432 -U gopher -d gopher_corpи посмотрим на результат:
\d employees; \d positionsТакже обратим внимание на таблицу schema_migrations:
SELECT *
FROM schema_migrations;Эта таблица автоматически созданная go-migrate, которая содержит информацию о текущем состоянии БД.
Попробуем вернуться к начальному состоянию:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
down -allЧто наблюдаем?
Попробуем заново применить миграцию:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
upЧто наблюдаем?
Исправим это. Установим версию в 1:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
force 1Проверим сосотояние:
SELECT *
FROM schema_migrations;Добавим код отменяющий миграцию в 00001_init.up.sql:
DROP TABLE employees;
DROP TABLE positions;"Откатим" миграцию:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
downЧто наблюдаем?
Попробуем снова:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
down -allМиграции лучше оборачивать в транзакции, в случае ошибки легче откатиться на предыдущую версию. Добавим транзакции в файлы миграций:
BEGIN TRANSACTION;
-- остальной код
COMMIT;Представим, что было решено добавить колонку email в таблицу employees. Давайте создадим для этого следующую миграцию:
docker run --rm \
-v $(realpath ./db/migrations-new):/migrations \
migrate/migrate:v4.18.1 \
create \
-dir /migrations-new \
-ext .sql \
-seq -digits 5 \
add_email_to_employeesИ добавим следующий код в 00002_add_email_to_employees.up.sql:
BEGIN TRANSACTION;
ALTER TABLE employees
ADD COLUMN email VARCHAR(200) NOT NULL;
COMMIT;Добавим также описание комманд для отмены изменений в 00002_add_email_to_employees.down.sql:
BEGIN TRANSACTION;
ALTER TABLE employees
DROP COLUMN email;
COMMIT;Применим миграцию:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
upДобавим данные в БД:
INSERT INTO positions(title)
VALUES ('Go developer');
INSERT INTO employees (first_name, last_name, salary, position, email)
VALUES ('Alice', 'Liddell', '100000', (SELECT id FROM positions WHERE title='Go developer'), 'alice.liddell@gopher-corp.com');Убедимся, что данные добавлены:
SELECT *
FROM employees;Представим теперь, что мы захотели откатиться на одну версию назад. Что случится с таблицей после выполнения следующей команды?
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
down 1Зависит от подхода, принятого в комманде.
Один из вариантов - не удалять даные, а помечать как удаленные.
Применим миграцию 2 из db/migrations-careful:
docker run --rm \
-v $(realpath ./db/migrations-careful):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
upЗапишем значение электронной почты сотрудника:
UPDATE employees
SET email='alice.liddell@gopher-corp.com'
WHERE first_name='Alice' AND last_name='Liddell';Теперь отменим последнюю миграцию:
docker run --rm \
-v $(realpath ./db/migrations-careful):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
down 1Посмотрим на таблицу, а затем опять применим миграцию:
docker run --rm \
-v $(realpath ./db/migrations-careful):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
upМиграции можно автоматически применять при старте приложения. См. файл internal/store/db.go.
Откатим миграции:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
--network webinar-migrations_yabinar-3 \
migrate/migrate:v4.18.1 \
-path=/migrations \
-database postgres://gopher:gopher@yabinar-3-postgres:5432/gopher_corp?sslmode=disable \
down -allЗапустите приложение:
make && ./cmd/employees/migrations -dsn "postgresql://gopher:gopher@localhost:5432/gopher_corp?sslmode=disable"Создадим еще раз должность:
INSERT INTO positions(title)
VALUES ('Go developer');Попробуем создать нового сотрудника:
TODO: fixme
curl --request POST http://127.0.0.1:8080/employee \
--header "Content-Type: application/json" \
--data '{"first_name": "Bob", "last_name": "Morane", "salary": 75000, "position": "Go developer", "email": "bob.morane@gopher-corp.com"}'Попробуем добавить еще одного сотрудника (с отрицательной зарплатой):
curl --request POST http://127.0.0.1:8080/employee \
--header "Content-Type: application/json" \
--data '{"first_name": "Charlie", "last_name": "Bucket", "salary": -75000, "position": "Go developer", "email": "charlie.bucket@gopher-corp.com"}' \
-vМы видим, что произошла ошибка. Это ожидаемо, т.к. БД проверяет положительность зарплаты. Было бы здорово тестировать это, чтобы убедиться, что этот функционал не будет утерян в результате регрессии.
См. internal/store/db_integration_test.go.
Запустить интеграционные тесты можно при помощи команды:
go test ./... -tags=integration_tests -v