環境ごとのDB差分を管理するFlywayの使い方

# はじめに

こんにちは！
バックエンドエンジニアの matsuura です。

[Gakken LEAP Advent Calendar 2024](https://qiita.com/advent-calendar/2024/gleap)の18日目の記事をお届けいたします！:birthday:

皆さんは、データベースマイグレーションやっていますか？

今回は、Flywayを使ったDocker環境でのマイグレーション実行や、環境差がある場合の対応に取り組んでみたので、その内容をご紹介していきます！

# 取り組んだ内容

## 【基本編】DockerでFlywayを実行する

:::note info
こちらで紹介している内容は、どの環境でも同じクエリが実行されるパターンです。
環境ごとにクエリを実行させたい場合は、スキップして[こちら](#応用編開発で環境差分がある場合の対応)まで進んでもらえたらと思います。
:::

### ディレクトリ構成

Flywayのマイグレーションファイルを管理するため、次のようなディレクトリ構成を採用しました。

```txt
flyway/
├── sql
│   ├── V1_0__create-table.sql
│   ├── V1_1__insert-initial-data.sql
│   └── V2_0__insert-additional-data.sql
```

### クエリ実行順について

Flywayでは、各クエリを以下のような命名にするように定められています。

```txt
V{バージョン名}__{説明}.sql
```

- バージョン名
  - `_`を区切り文字として設定します。上記例では「1_0」や「1_1」としている部分がこれにあたります。
  - 「1_0_2」のようにマイナーバージョンを追加することも可能です。
- 説明
  - バージョンの後ろに`__`(アンダースコアを2つ)をつけた際、それ以降は「説明」という扱いになります。
  - クエリの説明を記述しておきます。

Flywayはバージョン順にクエリを実行してくれます。

上の場合、
**V1_0 → V1_1 → V2_0**
という順で実行されます。

:::note warn
バージョンが重複すると、マイグレーション実行時にエラーが発生します。
ネーミングに注意しましょう。
:::

### compose.yaml

以下は、FlywayをDocker Composeで実行するためのcompose.yamlファイルのサンプルです。

```yaml
x-template: &flyway-template
  image: flyway/flyway
  volumes:
    - ./flyway/sql:/flyway/sql
  environment:
    - FLYWAY_URL=<データベースのURL>
    - FLYWAY_USER=root
    - FLYWAY_PASSWORD=root
services:
  flyway-migrate:
    <<: *flyway-template
    command: migrate
```

### 実行コマンド

次のコマンドを実行することで、マイグレーションが実施されます。

```bash
docker compose run --rm flyway-migrate
```

これで、`./flyway/sql`下のクエリが対象のDBで実行されます。

### マイグレーションの確認と取り消し

以下のようにcompose.yamlを変更しておくことで、マイグレーションの実行状況の確認や実行内容の取り消しを行うこともできます。

```diff_yaml
services:
  flyway-migrate:
    <<: *flyway-template
    command: migrate
+  flyway-clean:
+    <<: *flyway-template
+    command: clean
+  flyway-info:
+    <<: *flyway-template
+    command: info
```

実行コマンドは以下の通りです。

```bash
# 実行状況の確認
docker compose run --rm flyway-info
# マイグレーションの取り消し
docker compose run --rm flyway-clean
```

## 【応用編】開発で環境差分がある場合の対応

ここまでで、FlywayをDockerコンテナで実行し、マイグレーションすることが可能になりました。

ただ、このままでは、開発環境や検証環境、本番環境など、<font color="red">どの環境においても、同一のクエリが実行される</font>ことになってしまいます。
たとえば、どの環境にも共通で同一構成テーブルを作成しますが、画像を取得するURLなどを格納するカラムには、環境ごとに異なる値を設定したくなると思います。

ここでは、そんな環境差異がある場合の対応を紹介していきます。

### ディレクトリ構成(変更後)

以前の構成からcommonとdevelop、productionを追加しました。

```txt
flyway/
├── sql
│   ├── common
│   │   ├── V1_0__create-table.sql
│   │   ├── V1_1__insert-initial-data.sql
│   │   └── V2_0__insert-additional-data.sql
│   ├── develop
│   │   └── V1_2__insert-initial-data-dev.sql
│   └── production
│       └── V1_2__insert-initial-data-prod.sql
```

- **common**: 全ての環境で共通のクエリを格納
- **develop**: 開発環境に対してのみ実行させたいクエリを格納
- **production**: 本番環境に対してのみ実行させたいクエリを格納

### クエリ実行順について(変更後)

こちらの場合でも、バージョンで指定した順にクエリが実行されます。
`develop`においては、
**V1_0(common) → V1_1 (common) → V1_2 (develop) → V2_0(common)**
となります。

### compose.yaml差分

次のようにcompose.yamlで設定するボリュームマウントを変更することで、環境に対応したクエリを読み込ませることが可能になります。

#### 開発環境

```yaml
flyway:
  volumes:
    - ./flyway/sql/common:/flyway/sql/common
    - ./flyway/sql/develop:/flyway/sql/develop
```

#### 本番環境

```yaml
flyway:
  volumes:
    - ./flyway/sql/common:/flyway/sql/common
    - ./flyway/sql/production:/flyway/sql/production
```

これで、環境ごとに実行したいクエリをそれぞれ実行することが可能になります！:tada:

# 最後に

今回紹介したように、Flywayを活用することで、環境ごとに適したデータベースの状態を管理できるようになります。
ぜひ、参考にしてもらえたらと思います！:blossom:

## エンジニア募集

Gakken LEAP では教育をアップデートしていきたいエンジニアを絶賛大募集しています！！
ぜひお気軽にカジュアル面談へお越しください！！

https://gakken-leap.co.jp/recruit/