Skip to content

Код для вебинара "Миграции схем БД + интеграционное тестирование"

Notifications You must be signed in to change notification settings

s-shpak/webinar-migrations

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Запуск БД в контейнере

Для запуска БД в контейнере выполните:

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

About

Код для вебинара "Миграции схем БД + интеграционное тестирование"

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published