1. マイグレーションメカニズムの概要

1.1 マイグレーションの概念と役割

データベースのマイグレーションは、データモデルの変更をデータベース構造に同期させるプロセスであり、データの永続性にとって重要です。アプリケーションのバージョンが進化するにつれ、データモデルは頻繁に変更され、フィールドの追加や削除、インデックスの修正などが行われます。マイグレーションにより、これらの変更をバージョン管理された体系的な方法で管理し、データベース構造とデータモデルの整合性を確保することができます。

現代のWeb開発において、マイグレーションメカニズムは以下の利点を提供します。

  1. バージョン管理: マイグレーションファイルはデータベース構造の変更履歴を追跡し、各バージョンの変更を理解し、簡単にロールバックすることができます。
  2. 自動デプロイ: マイグレーションメカニズムを通じて、データベースのデプロイと更新を自動化し、手動介入の可能性とエラーのリスクを減らすことができます。
  3. チームコラボレーション: マイグレーションファイルにより、異なる開発環境でチームメンバーが同期されたデータベース構造を使用することができ、協力的な開発を促進します。

1.2 entフレームワークのマイグレーション機能

entフレームワークとマイグレーションメカニズムの統合により、次の機能が提供されます。

  1. 宣言型プログラミング: 開発者はエンティティのGo表現に焦点を当てるだけで、entフレームワークがエンティティをデータベーステーブルに変換します。
  2. 自動マイグレーション: entはDDL文を手動で記述する必要なく、データベーステーブル構造を自動的に作成および更新することができます。
  3. 柔軟な制御: entは、外部キー制約の有無やグローバルに一意なIDの生成など、さまざまなマイグレーション要件をサポートするための構成オプションを提供します。

2. 自動マイグレーションの概要

2.1 自動マイグレーションの基本原則

entフレームワークの自動マイグレーション機能は、スキーマ定義ファイル(通常はent/schemaディレクトリに存在)に基づいてデータベース構造を生成します。開発者がエンティティとリレーションシップを定義した後、entはデータベース内の既存構造を調査し、それに応じた操作を生成してテーブルの作成、列の追加または修正、インデックスの作成などを行います。

さらに、entの自動マイグレーション原則は「追加モード」で動作します。これは、デフォルトで新しいテーブル、新しいインデックスを追加したり、既存のテーブルや列を削除しない設計であり、誤ってデータの消失を防止し、データベース構造を順方向に拡張することが容易になります。

2.2 自動マイグレーションの使用方法

entの自動マイグレーションを使用する基本的な手順は次の通りです:

package main

import (
    "context"
    "log"
    "ent"
)

func main() {
    client, err := ent.Open("mysql", "root:pass@tcp(localhost:3306)/test")
    if err != nil {
        log.Fatalf("Failed to connect to MySQL: %v", err)
    }
    defer client.Close()
    ctx := context.Background()

    // データベーススキーマを作成または更新するための自動マイグレーションを実行
    if err := client.Schema.Create(ctx); err != nil {
        log.Fatalf("Failed to create database schema: %v", err)
    }
}

上記のコードでは、ent.Openがデータベースとの接続を確立してクライアントインスタンスを返し、client.Schema.Createが実際の自動マイグレーション操作を実行します。

3. 自動マイグレーションの高度な応用

3.1 列やインデックスの削除

場合によっては、データベーススキーマから不要になった列やインデックスを削除する必要があります。このような場合、WithDropColumnおよびWithDropIndexオプションを使用できます。例:

// 列やインデックスを削除するオプションを指定してマイグレーションを実行
err = client.Schema.Create(
    ctx,
    migrate.WithDropIndex(true),
    migrate.WithDropColumn(true),
)

このコードスニペットは、自動マイグレーション時に列やインデックスを削除する構成を有効にします。entは、マイグレーションを実行する際にスキーマ定義に存在しない任意の列やインデックスを削除します。

3.2 グローバルユニークID

デフォルトでは、SQLデータベースのプライマリキーは各テーブルに対して1から開始され、異なるエンティティ型が同じIDを共有することがあります。GraphQLを使用するなどのアプリケーションシナリオでは、異なるエンティティ型のオブジェクトのIDに対してグローバルな一意性を提供する必要があることがあります。entでは、これはWithGlobalUniqueIDオプションを使用して構成することができます。

// 各エンティティに対してユニバーサルなユニークIDを持つようにマイグレーションを実行
if err := client.Schema.Create(ctx, migrate.WithGlobalUniqueID(true)); err != nil {
    log.Fatalf("Failed to create the database schema: %v", err)
}

WithGlobalUniqueIDオプションを有効にすると、entはグローバルな一意性を達成するために、ent_typesという名前のテーブルに対して2^32の範囲のIDを各エンティティに割り当てます。

3.3 オフラインモード

オフラインモードでは、スキーマの変更をデータベース上で直接実行する代わりに、io.Writer に書き込みます。これは、変更が実際に適用される前にSQLコマンドを確認したり、手動で実行するためのSQLスクリプトを生成するのに役立ちます。例:

// マイグレーションの変更内容をファイルに書き込む
f, err := os.Create("migrate.sql")
if err != nil {
    log.Fatalf("マイグレーションファイルの作成に失敗しました: %v", err)
}
defer f.Close()
if err := client.Schema.WriteTo(ctx, f); err != nil {
    log.Fatalf("データベースのスキーマ変更の出力に失敗しました: %v", err)
}

このコードは、マイグレーションの変更内容を migrate.sql というファイルに書き込みます。実際には、開発者は直接標準出力に出力するか、レビューや記録目的でファイルに書き込むことができます。

4. 外部キーのサポートとカスタムフック

4.1 外部キーの有効化または無効化

Entでは、外部キーはエンティティ間の関係(エッジ)を定義することで実装され、これらの外部キー関係はデータベースレベルで自動的に作成され、データの整合性と一貫性を強制します。しかしながら、パフォーマンスの最適化やデータベースが外部キーをサポートしていない場合など、特定の状況では外部キーを無効にすることが選択されることがあります。

マイグレーションで外部キー制約を有効または無効にするには、WithForeignKeys 構成オプションを使用します:

// 外部キーの有効化
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(true),
)
if err != nil {
    log.Fatalf("外部キーを使用してスキーマリソースを作成できませんでした: %v", err)
}

// 外部キーの無効化
err = client.Schema.Create(
    ctx,
    migrate.WithForeignKeys(false),
)
if err != nil {
    log.Fatalf("外部キーを使用しないでスキーマリソースを作成できませんでした: %v", err)
}

この構成オプションは、Schema.Create を呼び出す際に渡す必要があり、指定された値に基づいて生成されたDDLに外部キー制約を含めるかどうかを決定します。

4.2 マイグレーションフックの適用

マイグレーションフックは、マイグレーションの実行のさまざまな段階で挿入されて実行されるカスタムロジックです。これは、マイグレーション前後などで、データベース上で特定のロジックを実行したり、マイグレーション結果を検証したり、データを事前に満たしたりするのに非常に便利です。

カスタムマイグレーションフックの実装例は次の通りです:

func customHook(next schema.Creator) schema.Creator {
    return schema.CreateFunc(func(ctx context.Context, tables ...*schema.Table) error {
        // マイグレーション前に実行されるカスタムコード
        // 例えば、ログ記録、特定の事前条件のチェックなど
        log.Println("マイグレーション前のカスタムロジック")

        // 次のフックまたはデフォルトのマイグレーションロジックを呼び出す
        err := next.Create(ctx, tables...)
        if err != nil {
            return err
        }

        // マイグレーション後に実行されるカスタムコード
        // 例えば、クリーンアップ、データ移行、セキュリティチェックなど
        log.Println("マイグレーション後のカスタムロジック")
        return nil
    })
}

// マイグレーションでカスタムフックを使用
err := client.Schema.Create(
    ctx,
    schema.WithHooks(customHook),
)
if err != nil {
    log.Fatalf("カスタムマイグレーションフックの適用中にエラーが発生しました: %v", err)
}

フックは複雑なマイグレーションに対してパワフルで不可欠なツールであり、必要に応じてデータベースマイグレーションの動作を直接制御できます。

5. バージョン管理されたマイグレーション

5.1 バージョン管理されたマイグレーションの紹介

バージョン管理されたマイグレーションは、データベースマイグレーションを管理するためのパターンであり、開発者がデータベース構造の変更を複数のバージョンに分割し、それぞれが特定のデータベース変更コマンドのセットを含むようにします。自動マイグレーションと比較して、バージョン管理されたマイグレーションはより細かい制御を提供し、データベース構造の変更のトレーサビリティと可逆性を確保します。

バージョン管理されたマイグレーションの主な利点は、前方および後方のマイグレーション(すなわち、アップグレードまたはダウングレード)をサポートし、開発者が必要に応じて特定の変更を適用、ロールバック、またはスキップすることが可能となります。チーム内での共同作業時、バージョン管理されたマイグレーションは各メンバーが同じデータベース構造で作業することを保証し、不整合による問題を軽減します。

自動マイグレーションは多くの場合、取り消しができないため、エンティティモデルの最新状態に合わせてSQLステートメントを生成および実行しますが、主に開発段階や小規模プロジェクトで使用されます。

5.2 バージョン管理されたマイグレーションの使用

1. Atlas ツールのインストール

バージョン管理されたマイグレーションを使用する前に、システムに Atlas ツールをインストールする必要があります。Atlas は複数のデータベースシステムをサポートするマイグレーションツールであり、データベーススキーマ変更の強力な機能を提供します。

macOS + Linux

curl -sSf https://atlasgo.sh | sh

Homebrew

brew install ariga/tap/atlas

Docker

docker pull arigaio/atlas
docker run --rm arigaio/atlas --help

Windows

https://release.ariga.io/atlas/atlas-windows-amd64-latest.exe

2. 現在のエンティティ定義に基づいたマイグレーションファイルの生成

atlas migrate diff migration_name \
  --dir "file://ent/migrate/migrations" \
  --to "ent://ent/schema" \
  --dev-url "docker://mysql/8/ent"

3. アプリケーションのマイグレーションファイル

マイグレーションファイルが生成されたら、これらを開発、テスト、または本番環境に適用できます。通常、これらのマイグレーションファイルをまず開発またはテストデータベースに適用して、期待どおりに実行されることを確認します。その後、同じマイグレーション手順を本番環境で実行します。

atlas migrate apply \
  --dir "file://ent/migrate/migrations" \
  --url "mysql://root:pass@localhost:3306/example"

atlas migrate apply コマンドを使用し、マイグレーションファイルのディレクトリ (--dir) と対象データベースのURL (--url) を指定してマイグレーションファイルを適用します。