Golang用のマイグレーションツール「migu」使ってみた

Goのコードからマイグレーションしてくれるツールないかなあと探していたところ、[migu](https://github.com/naoina/migu)を見つけました。
開発も止まってなさそうなので、とりあえず使ってみることにしました。（2021/2/17時点）

<b>サポートされているDBは「MariaDB / MySQL / Cloud Spanner」です。</b>
開発者の方が基本的にMariaDB/MySQLしか使わないためメンテナンスできないことが理由だそうです。
プルリクをもらったらサポートを考えるそうです。


#### Dockerで環境構築
``` Dockerfile
FROM golang:1.15.6-alpine

ENV GO111MODULE on

ADD . /migu_sample

WORKDIR /migu_sample

RUN apk update && apk add --no-cache git mariadb-dev mysql-client
RUN go get github.com/go-sql-driver/mysql && go get -u github.com/naoina/migu/cmd/migu

```

``` docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
    container_name: app_migu_sample
    volumes:
      - '.:/migu_sample/'
    ports:
      - '8000:8000'
    tty: true

  db:
    image: mariadb:latest
    container_name: db_migu_sample
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
    environment:
      - MYSQL_ROOT_USER=root
      - MYSQL_ROOT_PASSWORD=root
      - MYSQL_DATABASE=db_migu_sample
      - MYSQL_USER=user
      - MYSQL_PASSWORD=user
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - '3333:3306'

volumes:
  db_data:
    driver: local

```

#### スキーマを定義する
`+migu`の記述のある構造体がマイグレーション対象として認識されます。

``` models/user.go
package model

// +migu
type User struct {
	Username string
}

```

#### マイグレーション実行
コマンドは
`migu sync -u ユーザー名 -h ホスト名 -p データベース名 スキーマファイル（または　パッケージ）`

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go
# docker-compose exec app migu sync -u root -h db -p db_migu_sample model とするとmodel直下のファイル全てがマイグレーション対象

================ 実行結果 ================
--------applying--------
CREATE TABLE `user` (
  `username` VARCHAR(255) NOT NULL
)
```

もちろんテーブルが存在しない場合はテーブルを作成してくれます。
無事マイグレーション。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/dbf6d8c9-52db-2e8c-902c-35fa017881c2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/26252ba9-8d68-322e-c445-ea1b8dd784bb.png)

#### カラム追加
モデルに追加してマイグレーションを実行するだけです

``` models/user.go
package model

// +migu
type User struct {
	Username string
	Password string // 追加
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` ADD `password` VARCHAR(255) NOT NULL
--------done 0.005s--------
```

#### Primary Keyを定義
``` model/user.go
package model

// +migu
type User struct {
	ID       int64 `migu:"pk"`
	Username string
	Password string
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` ADD `id` BIGINT NOT NULL
--------done 0.006s--------
--------applying--------
ALTER TABLE `user` ADD PRIMARY KEY (`id`)
--------done 0.023s--------
--------done 0.005s--------
```

#### Primary Keyをオートインクリメントする
`autoincrement`をつけるだけです

``` model/user.go
// +migu
type User struct {
	...
	_ int64     `migu:"pk,column:id,autoincrement"`
	..
	.
}
```

#### ベースモデル的なものを作る
ただ、BaseModelを実装し、Userモデルに埋め込んでもDDLに反映されません。
「_（アンダースコア）」を使用して対応するフィールドを記述することで、DDLに反映されるようになるようです。

``` model/user.go
package model

type BaseModel struct {
	ID        int64
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt time.Time
}

// +migu
type User struct {
	Username string
	Password string

	BaseModel
	_ int64     `migu:"pk,column:id"` // column:~~ がないとマイグレーションを実行しても無視される
	_ time.Time `migu:"column:created_at"`
	_ time.Time `migu:"column:updated_at"`
	_ time.Time `migu:"column:deleted_at,null"`
}

```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` ADD `id` BIGINT NOT NULL COMMENT 'column:~~ がないとマイグレーションを実行しても無視される'
--------done 0.008s--------
--------applying--------
ALTER TABLE `user` ADD `created_at` DATETIME NOT NULL
--------done 0.005s--------
--------applying--------
ALTER TABLE `user` ADD `updated_at` DATETIME NOT NULL
--------done 0.013s--------
--------applying--------
ALTER TABLE `user` ADD `deleted_at` DATETIME
--------done 0.016s--------
--------applying--------
ALTER TABLE `user` ADD PRIMARY KEY (`id`)
--------done 0.138s--------
```

#### 文字列の最大長などを設定する
``` model/user.go
// +migu
type User struct {
	Username string
	Password string  `migu:"type:varchar(128)"`
	Weight   float64 `migu:"type:decimal(2,1)"`
}
```

``` shell
migu_sample % docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` CHANGE `password` `password` VARCHAR(128) NOT NULL
--------done 0.048s--------
--------applying--------
ALTER TABLE `user` ADD `weight` DECIMAL(2,1) NOT NULL
--------done 0.003s--------
```

#### Nullを許容する
カラムはデフォルトでNotNullになってしまうっぽいので、Nullを許容したいフィールドは明示的に記述する必要があるみたいです。

``` model/user.go
// +migu
type User struct {
	Username string
	Password string  `migu:"type:varchar(128)"`
	Weight   float64 `migu:"type:decimal(2,1),null"` // 明示的にnullを記述
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` CHANGE `weight` `weight` DECIMAL(2,1) // DDLからNOT NULLが消えた
--------done 0.025s--------
```

#### インデックスを張る
``` model/user.go
// +migu
type User struct {
	Username string  `migu:"index"` // index:user_index で任意のインデックス名にもできる
	...
	..
	.
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` CHANGE `username` `username` VARCHAR(255) NOT NULL
--------done 0.002s--------
--------applying--------
ALTER TABLE `user` CHANGE `id` `id` BIGINT NOT NULL
--------done 0.003s--------
--------applying--------
CREATE INDEX `user_username` ON `user` (`username`)
--------done 0.010s--------
```

#### ユニーク制約を設定する
``` model/user.go
// +migu
type User struct {
	Username string  `migu:"unique"`
	...
	..
	.
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` CHANGE `username` `username` VARCHAR(255) NOT NULL
--------done 0.001s--------
--------applying--------
DROP INDEX `user_username` ON `user`
--------done 0.018s--------
--------applying--------
CREATE UNIQUE INDEX `user_username` ON `user` (`username`)
--------done 0.009s--------
```

#### 登録日時カラムに自動で値が入るようにしたい
``` model/user.go
// +migu
type User struct {
	...
	_ time.Time `migu:"column:created_at,default:current_timestamp"`
	..
	.
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` CHANGE `created_at` `created_at` DATETIME NOT NULL DEFAULT current_timestamp
--------done 0.002s--------
```

#### 更新日時カラムに自動で値が入るようにしたい
初回登録時に「0000年0月0日」がはいってしまうので、Nullを許容してdefault:nullにしてます。
（default:current_timestampにしてしまっても良いと思います）

``` model/user.go
// +migu
type User struct {
	...
	_ time.Time `migu:"column:updated_at,null,default:null,extra:ON UPDATE CURRENT_TIMESTAMP"`
	..
	.
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` CHANGE `updated_at` `updated_at` DATETIME DEFAULT null ON UPDATE CURRENT_TIMESTAMP
--------done 0.046s--------
```

無事、登録日時/更新日時に値が入るようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/357470/165a3193-f46a-302c-35ae-bf1f36675e6a.png)


#### テーブル名を指定する
``` model/user.go
// +migu table:"auth_users"
type User struct {
	...
	..
	.
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
CREATE TABLE `auth_users` (
  `username` VARCHAR(255) NOT NULL,
  `password` VARCHAR(255) NOT NULL,
  `id` BIGINT NOT NULL COMMENT 'column:~~ がないとマイグレーションを実行しても無視される',
  `created_at` DATETIME NOT NULL,
  `updated_at` DATETIME NOT NULL,
  `deleted_at` DATETIME NOT NULL,
  PRIMARY KEY (`id`)
)
--------done 0.023s--------
```

#### フィールドにコメントを設定する
Goのコメント文を末尾にいれてるだけです。

``` model/user.go
// +migu
type User struct {
	Username string  `migu:"unique"`                 // ユーザー名
	Password string  `migu:"type:varchar(128)"`      // パスワード
	Weight   float64 `migu:"type:decimal(2,1),null"` // 体重
	Email    string  `migu:"type:varchar(254),null"` // メールアドレス

	BaseModel
	_ int64     `migu:"pk,column:id,autoincrement"`                                            // 主キー
	_ time.Time `migu:"column:created_at,default:current_timestamp"`                           // 登録日時
	_ time.Time `migu:"column:updated_at,null,default:null,extra:ON UPDATE CURRENT_TIMESTAMP"` // 最終更新日時
	_ time.Time `migu:"column:deleted_at,null"`                                                // 削除日時
}
```

``` shell
docker-compose exec app migu sync -u root -h db -p db_migu_sample model/user.go

================ 実行結果 ================
--------applying--------
ALTER TABLE `user` CHANGE `username` `username` VARCHAR(255) NOT NULL COMMENT 'ユーザー名'
--------done 0.002s--------
--------applying--------
ALTER TABLE `user` CHANGE `password` `password` VARCHAR(128) NOT NULL COMMENT 'パスワード'
--------done 0.013s--------
--------applying--------
ALTER TABLE `user` CHANGE `weight` `weight` DECIMAL(2,1) COMMENT '体重'
--------done 0.001s--------
--------applying--------
ALTER TABLE `user` CHANGE `email` `email` VARCHAR(254) COMMENT 'メールアドレス'
--------done 0.001s--------
--------applying--------
ALTER TABLE `user` CHANGE `id` `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主キー'
--------done 0.001s--------
--------applying--------
ALTER TABLE `user` CHANGE `created_at` `created_at` DATETIME NOT NULL DEFAULT current_timestamp COMMENT '登録日時'
--------done 0.002s--------
--------applying--------
ALTER TABLE `user` CHANGE `updated_at` `updated_at` DATETIME DEFAULT null ON UPDATE CURRENT_TIMESTAMP COMMENT '最終更新日時'
--------done 0.002s--------
--------applying--------
ALTER TABLE `user` CHANGE `deleted_at` `deleted_at` DATETIME COMMENT '削除日時'
--------done 0.002s--------
```

# 所感
コードで差分見てマイグレーションしてくれるし、マイグレーションファイルはいらないしで、個人的にはすごく使いやすく感じました。
（スター数が圧っっっっっっっっっっっっっっっっっっっっっっっっ倒的に少ないですが）

個人的にはもっと伸びてほしいマイグレーションツールだなと感じました・・・。