🎉

Gorm/Gormigrateを利用したMigrationについて

2021/12/18に公開

この記事は、株式会社アトラエのアドベントカレンダー2021年18日目の記事です。

株式会社アトラエのエンジニアの@takayです!
現在シニア向けマッチングサービスInowのバックエンドエンジニアをしております。InowサービスのmainのバックエンドサービスはGo言語を採用しており、Gorm/Gormigrateのライブラリーを利用したMigrationを実装したときの話を下記で記載していきます。

Gormigrateについて

Migrationを実装するにあたって要件はテーブルの追加/削除・カラムの追加/変更/削除・indexの追加削除等々当然ありますが、InowではORMとしてGormを利用しており基本的にはGormのMigrationの機能を利用しようと考えていました。

schemaのversionningやrollback等の機能も運用を考慮すると必要と考えていましたが、Gormのライブラリーではそのような機能が提供されていないため、色々と調べた結果GormigrateというライブラリーがGormのmigrationのhelper的な機能を提供しており利用をすることとしました
https://github.com/go-gormigrate/gormigrate

実装について

GormigrateのReadmeに以下のように書かれています

main.go
package main

import (
	"log"

	"github.com/go-gormigrate/gormigrate/v2"
	"gorm.io/gorm"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
)

func main() {
	db, err := gorm.Open("sqlite3", "mydb.sqlite3")
	if err != nil {
		log.Fatal(err)
	}

	m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{
		{
			ID: "201608301400",
			Migrate: func(tx *gorm.DB) error {
				type Hoge struct {
					gorm.Model
					Name string
				}

				return tx.AutoMigrate(&Hoge{})
			},
			Rollback: func(tx *gorm.DB) error {
				return tx.Migrator().DropTable("hoges")
			},
		},
	})

	if err = m.Migrate(); err != nil {
		log.Fatalf("Could not migrate: %v", err)
	}
	log.Printf("Migration did run successfully")
}

Optionsでversioningに必要なテーブルの名前が指定できたり(defaultではmigrationsというテーブル)、migrationIdのカラムの名前/長さなどが指定できます。

特定のversionまでMigrationしたい場合にはMigrateToというメソッドが用意されています。

gormigrate.go
func (g *Gormigrate) MigrateTo(migrationID string) error {
	if err := g.checkIDExist(migrationID); err != nil {
		return err
	}
	return g.migrate(migrationID)
}

直前のMigrationをrollbackさせたい場合にはRollbackLastというメソッドがあります。

gormigrate.go
// RollbackLast undo the last migration
func (g *Gormigrate) RollbackLast() error {
	if len(g.migrations) == 0 {
		return ErrNoMigrationDefined
	}

	g.begin()
	defer g.rollback()

	lastRunMigration, err := g.getLastRunMigration()
	if err != nil {
		return err
	}

	if err := g.rollbackMigration(lastRunMigration); err != nil {
		return err
	}
	return g.commit()
}

直前の特定のrollbackさせたい場合にはRollbackLastというメソッドを利用します。

gormigrate.go
// RollbackTo undoes migrations up to the given migration that matches the `migrationID`.
// Migration with the matching `migrationID` is not rolled back.
func (g *Gormigrate) RollbackTo(migrationID string) error {
	if len(g.migrations) == 0 {
		return ErrNoMigrationDefined
	}

	if err := g.checkIDExist(migrationID); err != nil {
		return err
	}

	g.begin()
	defer g.rollback()

	for i := len(g.migrations) - 1; i >= 0; i-- {
		migration := g.migrations[i]
		if migration.ID == migrationID {
			break
		}
		migrationRan, err := g.migrationRan(migration)
		if err != nil {
			return err
		}
		if migrationRan {
			if err := g.rollbackMigration(migration); err != nil {
				return err
			}
		}
	}
	return g.commit()
}

その他

Gormigrateにはmigration用のファイルを自動で生成するみたいな機能(railsで言うとrails generate migration AddFugoToHogesで作られるファイルやつ)は無いので、こちらはgo generate等を使ってファイルを自動生成できるようにしました。この実装についてはまた別の機会にでも書こうと思います。

Discussion