sqldefで簡単にデータベーススキーマをマイグレーションする方法

この記事は[ZOZO Advent Calendar 2024](https://qiita.com/advent-calendar/2024/zozo) シリーズ6 18日目の記事になります。

# はじめに
`sqldef`について調べたので、データベーススキーマをマイグレーションする方法をまとめてみました。

# sqldefとは
`sqldef`は、データベースのスキーマを定義ファイルに基づいて自動的に更新するツールです。MySQL、PostgreSQL、SQLiteなど複数のデータベースをサポートしており、手動でスキーマを変更する手間を省き、バージョン管理されたスキーマ定義を簡単に適用できます。 

# プロジェクトのセットアップ
以下のコマンドでプロジェクトディレクトリと必要なサブディレクトリを作成します。

```sh
# プロジェクトディレクトリを作成
mkdir sqldef-example
cd sqldef-example

# 必要なディレクトリを作成
mkdir -p docker/sqldef database
```

# `Dockerfile`の作成
`docker/sqldef/Dockerfile`を以下の内容で作成します。
Dockerfileでは`sqldef`と`dockerize`をインストールし、エントリーポイントスクリプトを設定します。

```dockerfile
# ベースイメージとしてAlpine Linuxを使用
FROM alpine

# 作業ディレクトリを設定
WORKDIR /tmp

# 必要なツールをインストール
RUN apk add --update --no-cache curl

# sqldefをダウンロードしてインストール
RUN curl -LO https://github.com/k0kubun/sqldef/releases/download/v0.17.24/mysqldef_linux_amd64.tar.gz && \
    tar xvf mysqldef_linux_amd64.tar.gz && \
    mv /tmp/mysqldef /usr/bin/mysqldef

# dockerizeをダウンロードしてインストール
RUN curl -L https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-alpine-linux-amd64-v0.6.1.tar.gz -o dockerize-alpine-linux-amd64.tar.gz && \
    tar xvf dockerize-alpine-linux-amd64.tar.gz && \
    mv /tmp/dockerize /usr/bin/dockerize

# エントリーポイントスクリプトをコピー
COPY docker/sqldef/entrypoint.sh /entrypoint.sh

# エントリーポイントスクリプトに実行権限を付与
RUN chmod +x /entrypoint.sh

# コンテナ起動時に実行するコマンドを設定
ENTRYPOINT ["/entrypoint.sh"]

```

# スキーマ定義ファイルの作成
`database/create_tables.sql`を以下の内容で作成します。
データベースに適用するスキーマ定義を記載します。

```sql
-- usersテーブルを作成
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

# エントリーポイントスクリプトの作成
`docker/sqldef/entrypoint.sh`を以下の内容で作成します。
このスクリプトはコンテナ起動時に実行され、MySQLが準備完了するのを待ってから`sqldef`を実行し、スキーマを適用します。

```sh
#!/bin/sh

# MySQLが準備完了するのを待つ
echo "Waiting for MySQL to be ready..."
dockerize -wait tcp://$MYSQL_HOST:$MYSQL_TCP_PORT -timeout 60s

# sqldefを実行してスキーマを適用
echo "Running sqldef..."
mysqldef --user=$MYSQL_USER --password=$MYSQL_PWD --host=$MYSQL_HOST $MYSQL_DATABASE < $SQL_FILE_TO_MIGRATE_APPLICATION_DATABASE
```

# Docker Composeの設定
`docker-compose.yml`をプロジェクトのルートディレクトリに以下の内容で作成します。
MySQLとアプリケーションのコンテナを定義し、環境変数を設定します。

```yaml
version: '3.8'

services:
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: sqldef-example-db
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"

  app:
    build:
      context: .
      dockerfile: docker/sqldef/Dockerfile
    environment:
      MYSQL_DATABASE: sqldef-example-db
      MYSQL_USER: user
      MYSQL_PWD: password
      MYSQL_HOST: mysql
      MYSQL_TCP_PORT: 3306
      SQL_FILE_TO_MIGRATE_APPLICATION_DATABASE: "/mysql/create_tables.sql"
    volumes:
      - ./database:/mysql
```

# 実行
以下のコマンドを実行してコンテナを起動し、マイグレーションを実行します。

```sh
docker-compose up
```

# 接続確認
以下のコマンドを実行して、MySQLクライアントを使用して接続します。

```sh
docker exec -it sqldef-example-mysql-1 mysql -u user -p
```

パスワードが求められたら、`docker-compose.yml`で設定した `MYSQL_PASSWORD` (`password`) を入力します。

接続後、以下のコマンドを実行します。

```sql
SHOW DATABASES;
USE sqldef-example-db;
SHOW TABLES;
DESCRIBE users;
```

`users` テーブルが表示されれば、接続とスキーマの作成が成功しています。
これで、`sqldef`を使用したデータベーススキーマのマイグレーションが実行され、動作確認まで完了しました。

#### 参考
- [sqldef GitHubリポジトリ](https://github.com/k0kubun/sqldef)
- [Dockerize GitHubリポジトリ](https://github.com/jwilder/dockerize)