goのORMのentとデータベースのschema管理ツールatlasでVersioned Migrationする
sampleappディレクトリにアプリケーションを作成していく。データベース関連はdatasourceディレクトリにまとめることにした。
mkdir -p ~/go/src/github.com/renoinn/sampleapp
cd ~/go/src/github.com/renoinn/sampleapp
go mod init
mkdir datasource
cd datasource
go run -mod=mod entgo.io/ent/cmd/ent init Users --target ./datasource/ent/schema
こんな感じでファイルが出力される。
package schema
import "entgo.io/ent"
// User holds the schema definition for the Users entity.
type Users struct {
ent.Schema
}
// Fields of the Users.
func (Users) Fields() []ent.Field {
return []ent.Field {
field.String("name"),
field.String("email").Unique()
}
}
// Edges of the Users.
func (Users) Edges() []ent.Edge {
return nil
}
フィールドの部分にカラムを追加してgo generate
する
...
// Fields of the Users.
func (Users) Fields() []ent.Field {
return []ent.Field {
field.String("name"),
field.String("email").Unique()
}
}
...
テーブル名と型名が同名じゃない場合はアノテーションで設定する。
// User Annotations
func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.Annotation{Table: "Users"},
}
}
go generate ./datasource/ent
./ent以下に自動生成されたファイルがたくさんできるはず。
あとは接続する処理を書く。
package main
import (
"context"
"log"
"github.com/renoinn/sampleapp/datasource/ent"
_ "github.com/go-sql-driver/mysql"
)
func main() {
client, err := ent.Open("mysql", "<user>:<pass>@tcp(<host>:<port>)/<database>?parseTime=True")
if err != nil {
log.Fatalf("failed opening connection to mysql: %v", err)
}
defer client.Close()
// Run the auto migration tool.
if err := client.Schema.Create(context.Background()); err != nil {
log.Fatalf("failed creating schema resources: %v", err)
}
}
このmain.goを起動したらマイグレーションされる。
ここからatlasで変更管理をしていく。
今回はUbuntuなので、以下のコマンドでatlasをインストールする。
curl -LO https://release.ariga.io/atlas/atlas-linux-amd64-latest
sudo install -o root -g root -m 0755 ./atlas-linux-amd64-latest /usr/local/bin/atlas
こんな感じのコマンドでschemaをhcl形式で吐き出してくれたりする。
atlas schema inspect -u "mysql://<user>:<password>@localhost:3306/<dbname>" > schema.hcl
generate.goに--feature sql/versioned-migration
のオプションを追加してgo generateする。
package ent
//go:generate go run -mod=mod entgo.io/ent/cmd/ent generate --feature sql/versioned-migration ./schema
go generate ./datasource/ent
すると、datasource/ent/migrateに差分を取るためのメソッドが追加されるので、それを使うようにする。
package main
import (
"context"
"fmt"
"log"
"os"
atlas "ariga.io/atlas/sql/migrate"
"entgo.io/ent/dialect"
"entgo.io/ent/dialect/sql/schema"
_ "github.com/go-sql-driver/mysql"
"github.com/renoinn/sampleapp/datasource/ent/migrate"
)
func main() {
dataSourceName := fmt.Sprintf("mysql://%s:%s@%s/%s?charset=utf8&parseTime=True", "sample_user", "sample_password", "localhost:3306", "sample_db")
ctx := context.Background()
// Create a local migration directory able to understand Atlas migration file format for replay.
dir, err := atlas.NewLocalDir("datasource/ent/migrate/migrations")
if err != nil {
log.Fatalf("failed creating atlas migration directory: %v", err)
}
// Migrate diff options.
opts := []schema.MigrateOption{
schema.WithDir(dir), // provide migration directory
schema.WithMigrationMode(schema.ModeReplay), // provide migration mode
schema.WithDialect(dialect.MySQL), // Ent dialect to use
schema.WithFormatter(atlas.DefaultFormatter),
}
// Generate migrations using Atlas support for MySQL (note the Ent dialect option passed above).
err = migrate.NamedDiff(ctx, dataSourceName, os.Args[1], opts...)
if err != nil {
log.Fatalf("failed generating migration file: %v", err)
}
}
こんな感じに書き換えて実行する。datasource/ent/migrate/migrations
ディレクトリは事前に作っておく。
go run -mod=mod ./cmd/migration/main.go create_users
- datasource/ent/migrate/migrations/20221104140216_create_users.sql
- datasource/ent/migrate/migrations/atlas.sum
の2ファイルが生成される。
-- create "users" table
CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `email` (`email`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
Ent的には生成されたSQLをAtlasのCLIで実行するのがオススメらしい。
atlas migrate apply \
--dir "file://datasource/ent/migrate/migrations"
--url mysql://sample_user:sample_password@localhost:3306/sample_db
Migrating to version 20221104140216 (1 migrations in total):
-- migrating version 20221104140216
-> CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `email` (`email`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
-- ok (75.061732ms)
-------------------------
-- 88.741096ms
-- 1 migrations
-- 1 sql statements
atlas.sumはマイグレーションの競合を検出しやすくするためのファイルらしい。
2つのチームが別々のブランチでマイグレーションを更新している場合、先にマージした方のatlas.sumに対して、後からマージする側がコンフリクトすることになるので、この段階で両チームがマイグレーションに変更を加えようとしてることを検出できると。
ent + atlasで生成されたファイルに後から手動で変更を加えていないかどうかは下記のコマンドでチェックできる。
atlas migrate validate --dir file://<path-to-your-migration-directory>
全体のソースコードはこんな感じ。
s/sampleapp/ent_atlas_sample/
マイグレーションを追加する。
cmd/migration/main.goのMigrateOptionでMigrationModeをModeInspectに修正する。
opts := []schema.MigrateOption{
schema.WithDir(dir), // provide migration directory
schema.WithMigrationMode(schema.ModeInspect), // provide migration mode
schema.WithDialect(dialect.MySQL), // Ent dialect to use
schema.WithFormatter(atlas.DefaultFormatter),
}
go run -mod=mod entgo.io/ent/cmd/ent init Site --target ./datasource/ent/schema
(./datasource/ent/schema/site.goを編集)
go generate ./datasource/ent
go run -mod=mod ./cmd/migration/main.go create_site
一度マイグレーション用のSQLファイルを生成した後に、フィールドなどを書き換える
go run -mod=mod ./cmd/migration/main.go create_site
するとdatasource/ent/migrate/migrationsに20221108025948_create_site.sqlが追加されて、atlas.sumが更新される。
で、この時に生成されたSQLを見て、「やっぱりこうしよう」みたいな感じで./datasource/ent/schema/site.goを修正して、再度
go generate ./datasource/ent
go run -mod=mod ./cmd/migration/main.go create_site
を実行すると、20221108025948_create_site.sqlが変更されるのではなく、datasource/ent/migrate/migrationsに20221108030142_create_site.sqlが追加される。
atlas migrate apply --dir "file://datasource/ent/migrate/migrations" --url mysql://user:password@localhost:3306/sample_db --dry-run
Migrating to version 20221108030142 (2 migrations in total):
-- migrating version 20221108025948
-> CREATE TABLE `sites` (`id` bigint NOT NULL AUTO_INCREMENT, `url` varchar(2048) NOT NULL, `title` varchar(100) NOT NULL, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
-> CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `email` (`email`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
-- ok (1.836131ms)
-- migrating version 20221108030142
-> CREATE TABLE `sites` (`id` bigint NOT NULL AUTO_INCREMENT, `url` varchar(2048) NOT NULL, `title` varchar(100) NOT NULL, PRIMARY KEY (`id`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
-> CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(100) NOT NULL, PRIMARY KEY (`id`), UNIQUE INDEX `email` (`email`)) CHARSET utf8mb4 COLLATE utf8mb4_bin;
-- ok (1.887923ms)
atlas.sumの中身はこんな感じになる。
h1:ot0oyFCmaDgVv5CexXOAnjDBlxjBWm6ypxeBtryxswY=
20221108025948_create_site.sql h1:P3lh3alKiSWJyatvaBPlRsv108Nj3gdzNOtB13Dn4HQ=
20221108030147_create_site.sql h1:P3lh3alKiSWJyatvaBPlRsv108Nj3gdzNOtB13Dn4HQ=
この状態でapplyしようとしてもおかしなことになるので、どうにかする。
まず、不要な方のSQLファイルを削除する。
rm -rf datasource/ent/migrate/migrationsに20221108025948_create_site.sql
削除したら、atlas.sumを編集して削除した方のSQLファイルの行を削除する。
h1:ot0oyFCmaDgVv5CexXOAnjDBlxjBWm6ypxeBtryxswY=
- 20221108025948_create_site.sql h1:P3lh3alKiSWJyatvaBPlRsv108Nj3gdzNOtB13Dn4HQ= この行を削除
20221108030147_create_site.sql h1:P3lh3alKiSWJyatvaBPlRsv108Nj3gdzNOtB13Dn4HQ=
削除したらhashを生成し直す。
atlas migrate hash --dir "file://datasource/ent/migrate/migrations"
改めてdry-runして確認して問題なければapplyできる。
DB作成のdocker。
version: "3"
services:
mysql:
image: mysql:8.0
container_name: mysql
command: mysqld --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
restart: always
ports:
- 3306:3306
environment:
MYSQL_DATABASE: db_name
MYSQL_USER: user
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: root
app:
container_name: "app"
image: cosmtrek/air
working_dir: /var/app
volumes:
- ../.:/var/app
tty: true
ports:
- "8080:8080"
docker compose -f build/docker-compose.yml up -d --build