golang-migrate で PostgreSQL のデータベースに対してマイグレーションを適用する

# golang-migrate について

golang-migrate とは Go で書かれたデータベースマイグレーションツールです。

https://github.com/golang-migrate/migrate

# golang-migrate を使用してデータベースマイグレーションを適用する

今回はタスクを管理するテーブルを作成するシンプルなデータベースマイグレーションを例に説明します。

以下の環境変数は設定済みである前提で説明します。
- `POSTGRES_USER`
- `POSTGRES_PASSWORD`

## Docker Compose 設定

Docker Compose で postgres service と migrate service を設定します。

```compose.yaml
services:
  postgres:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
  migrate:
    image: migrate/migrate:v4.18.1

volumes:
  postgres_data:
```

### posgres service

以下の postgres image を指定した service です。

https://hub.docker.com/_/postgres

### migrate service

以下の migrate/migrate image を指定した service です。

https://hub.docker.com/r/migrate/migrate

migrate/migrate image は golang-migrate の Docker image です。

## マイグレーションファイル作成

### マイグレーションファイル作成実行

以下のコマンドを実行し、マイグレーションファイルを作成します。

```shell
docker compose run --rm -v ${PWD}/db/migrations:/migrations migrate create \
    -ext sql \
    -dir /migrations \
    -seq create_tasks
```

- `seq` オプションに指定した値が作成されるマイグレーションファイル名の一部になります。

実行結果 (抜粋)

```shell
/migrations/000001_create_tasks.up.sql
/migrations/000001_create_tasks.down.sql
```

### 作成されたファイルの確認

`db/migrations` ディレクトリ配下に以下の2つのファイルが作成されます。

- `000001_create_tasks.up.sql`
- `000001_create_tasks.down.sql`

コマンド実行時に `db/migrations` ディレクトリが存在しない場合、ディレクトリが作成されます。

## マイグレーションファイルの実装

作成されたマイグレーションファイルを実装します。

`xxx.up.sql` には適用したいマイグレーションの SQL を実装し、`xxx.down.sql` には `xxx.up.sql` のマイグレーション適用後に `xxx.up.sql` のマイグレーション適用を元に戻す SQL を実装します。

```000001_create_tasks.up.sql
CREATE TABLE IF NOT EXISTS tasks(
    id uuid PRIMARY KEY,
    title varchar(100) NOT NULL
);
```

```000001_create_tasks.down.sql
DROP TABLE IF EXISTS tasks;
```

## postgres serivce の起動

以下のコマンドを実行し、postgres service を起動します。

```shell
docker compose up postgres
```

後続のマイグレーション適用でマイグレーション対象の postgres serivce が起動している必要があるためです。

## マイグレーション (up) 適用

### マイグレーション適用前のデータベースの確認

```sql
postgres=# \dt
Did not find any relations.
```

### マイグレーション適用

以下のコマンドを実行し、マイグレーションを適用します。

```shell
docker compose run --rm -v ${PWD}/db/migrations:/migrations migrate \
    -database "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/postgres?sslmode=disable" \
    -path /migrations/ \
    up
```

実行結果

```shell
1/u create_tasks (5.998201ms)
```

### マイグレーション適用後のデータベースの確認

```sql
postgres=# \dt
               List of relations
 Schema |       Name        | Type  |  Owner   
--------+-------------------+-------+----------
 public | schema_migrations | table | postgres
 public | tasks             | table | postgres
(2 rows)
```

```sql
postgres=# SELECT * FROM schema_migrations;
 version | dirty 
---------+-------
       1 | f
(1 row)
```

```sql
postgres=# \d tasks;
                       Table "public.tasks"
 Column |          Type          | Collation | Nullable | Default 
--------+------------------------+-----------+----------+---------
 id     | uuid                   |           | not null | 
 title  | character varying(100) |           | not null | 
Indexes:
    "tasks_pkey" PRIMARY KEY, btree (id)
```

## マイグレーション (down) 適用
適用済みのマイグレーションを元に戻す場合は、マイグレーション (down) を適用します。

先ほど適用したマイグレーション (up) を元に戻します。

### マイグレーション適用

以下のコマンドを実行し、マイグレーションを適用します。

```shell
docker compose run --rm -v ${PWD}/db/migrations:/migrations migrate \
    -database "postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/postgres?sslmode=disable" \
    -path /migrations/ \
    down
```

実行結果

```shell
Are you sure you want to apply all down migrations? [y/N]
y
Applying all down migrations
1/d create_tasks (41.101533ms)
```

コマンド実行時に全てのダウンマイグレーションを適用しても良いか確認されるので、問題ない場合は `y` を選択し、適用を進めます。

### マイグレーション適用後のデータベースの確認

```sql
postgres=# \dt
               List of relations
 Schema |       Name        | Type  |  Owner   
--------+-------------------+-------+----------
 public | schema_migrations | table | postgres
(1 row)
```

```sql
postgres=# SELECT * FROM schema_migrations;
 version | dirty 
---------+-------
(0 rows)
```

先ほど適用したマイグレーションが元に戻っていることが分かります。