Для запуска БД в контейнере выполните:
make pg
Для остановки:
make stop-pg
Для удаления данных из БД:
make clean-data
Создадим первую миграцию:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
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'а:
docker inspect praktikum-webinar-db | grep IPAddress
После этого выполним:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]: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 \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
down -all
Что наблюдаем?
Попробуем заново применить миграцию:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
up
Что наблюдаем?
Исправим это. Установим версию в 1
:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]: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 \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
down
Что наблюдаем?
Попробуем снова:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
down -all
Миграции лучше оборачивать в транзакции, в случае ошибки легче откатиться на предыдущую версию. Добавим транзакции в файлы миграций:
BEGIN TRANSACTION;
-- остальной код
COMMIT;
Представим, что было решено добавить колонку email
в таблицу employees
. Давайте создадим для этого следующую миграцию:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
create \
-dir /migrations \
-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 \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]: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'), '[email protected]');
Убедимся, что данные добавлены:
SELECT *
FROM employees;
Представим теперь, что мы захотели откатиться на одну версию назад. Что случится с таблицей после выполнения следующей команды?
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
down 1
Зависит от подхода, принятого в комманде.
Один из вариантов - не удалять даные, а помечать как удаленные.
Применим миграцию 2
из db/migrations-careful
:
docker run --rm \
-v $(realpath ./db/migrations-careful):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
up
Запишем значение электронной почты сотрудника:
UPDATE employees
SET email='[email protected]'
WHERE first_name='Alice' AND last_name='Liddell';
Теперь отменим последнюю миграцию:
docker run --rm \
-v $(realpath ./db/migrations-careful):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
down 1
Посмотрим на таблицу, а затем опять применим миграцию:
docker run --rm \
-v $(realpath ./db/migrations-careful):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
up
Миграции можно автоматически применять при старте приложения. См. файл app/internal/store/db.go
.
Откатим миграции:
docker run --rm \
-v $(realpath ./db/migrations):/migrations \
migrate/migrate:v4.16.2 \
-path=/migrations \
-database postgres://gopher:[email protected]:5432/gopher_corp?sslmode=disable \
down -all
Запустите приложение, для этого выполните эту комманду из папки app
:
make && ./cmd/migrations/migrations -dsn "postgresql://gopher:gopher@localhost:5432/gopher_corp?sslmode=disable"
Создадим еще раз должность:
INSERT INTO positions(title)
VALUES ('Go developer');
Попробуем создать нового сотрудника:
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": "[email protected]"}'
Попробуем добавить еще одного сотрудника (с отрицательной зарплатой):
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": "[email protected]"}' \
-v
Мы видим, что произошла ошибка. Это ожидаемо, т.к. БД проверяет положительность зарплаты. Было бы здорово тестировать это, чтобы убедиться, что этот функционал не будет утерян в результате регрессии.
См. app/internal/store/db_integration_test.go