sql-migrateとGormでマイグレーションとシードを実装する

Goのボイラープレートを実装していたときの備忘録として残しておく。
まず今回使用するパッケージについて

sql-migrate
sql-migrateはその名の通り、SQLをマイグレートするためのツールでGoで作られています。マイグレーションするためのパッケージはいくつか存在していますが、個人的にsql-migrateがシンプルで使いやすかったという理由で採用しています。

gorm
続いてgormはGo製のORMパッケージになります。言わずもがなこちらもポピュラーなパッケージになります。他にもSQLBoiler,xormといったパッケージが存在しているようですが、最もGithubのスター数が多いことを理由にGORMを使用します。

GORMにもモデル定義に合わせた自動マイグレーション機能が用意されていますが、不足しているカラムやインデックスの生成はするが、カラム削除まではやってくれないなど、使っていくには不十分な点がります。そのため一番使いやすかったsql-migrateを使用しています。

実装

では実装に入っていきます。
使用するDBはPostgresqlでも構いませんが自分が使い慣れたMySQL使用することにします。

ディレクトリ構成は以下

├── dbconfig.yml
├── Dockerfile
├── docker-compose.yml
└── db
    ├── migrations
    ├── initdb.d
    │   └── create_db.sql
    ├── mysql_data
    └── seeds
        └── seed.go

まずはdockerで開発環境準備を。

docker-compose.yml

version: '3'
services:
  app:
    build: .
    container_name: go_container
    tty: true
    volumes:
      - ./:/app
    working_dir: /app
    ports:
      - "8080:8080"
    depends_on:
      - db
  db:
    image: mysql:5.7
    container_name: go_db
    volumes:
        - ./db/mysql_data:/var/lib/mysql
        - ./db/initdb.d:/docker-entrypoint-initdb.d
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: root
      MYSQL_PASSWORD: root
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

データベースを作成するためのSQL
db/initdb.d/create_db.sql

CREATE DATABASE IF NOT EXISTS api_db;

もしも立ち上げた時に初期実行ファイルが実行されない場合、永続化しているディレクトリ(mysql_data)を削除して再び立ち上げててください。永続化ディレクトリが既に存在している場合は初期実行の処理が行われません。

Dockerfile

FROM golang:latest
RUN go get github.com/go-sql-driver/mysql
RUN go get github.com/jinzhu/gorm
RUN go get github.com/rubenv/sql-migrate/...

EXPOSE 8080

今回は使用するパッケージが少ないので、Dockerfileに使用するパッケージをそのまま書いています。本来はmodを使うと便利です。

では続いてsql-migrateをDBに接続するためdbconfig.ymlを作成します

dbconfig.yml

development:
  dialect: mysql
  datasource: root:root@tcp(go_db:3306)/api_db?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true
  dir: db/migrations
  table: migrations

もしもPostgresqlに繋ぎたい場合は以下のように書いてください
dbconfig.yml

development:
  dialect: postgres
  datasource: host=go_db user=root port=5432 password=root sslmode=disable
  dir: db/migrations
  table: migrations

これでした準備ができたのでコンテナに入って作業をします

$ docker-compose up -d
$ docker-compose exec app bash

sql-migrateで使用できるのは以下のコマンドになります

$ sql-migrate status // ステータス確認 実行されているmigrate fileを確認可能
$ sql-migrate new articles // migration file作成
$ sql-migrate up // migration を実行
$ sql-migrate down // migrationをrollback

以下を実行してarticleのマイグレーションファイルを作成します

$ sql-migrate new articles

db/migrationsにコメントが記載された空のファイルが作成されます。
そこにSQLを書いて行きます。

-- +migrate Up
CREATE TABLE IF NOT EXISTS articles (
    id bigint AUTO_INCREMENT NOT NULL,
    title VARCHAR(255),
    body TEXT,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (id)
);

-- +migrate Down
DROP TABLE IF EXISTS articles;

マイグレーション実行

$ sql-migrate up

次にシードファイルを作成するためのseed.goを書きます。

package main

import (
    "fmt"
    "log"
    "time"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

// Article is
type Article struct {
    Title     string
    Body      string
    CreatedAt time.Time
    UpdatedAt time.Time
}

func seeds(db *gorm.DB) error {
    for i := 0; i < 10; i++ {
        article := Article{Title: "title", Body: "body", CreatedAt: time.Now(), UpdatedAt: time.Now()}
        if err := db.Create(&article).Error; err != nil {
            fmt.Printf("%+v", err)
        }
    }
    return nil
}

func openConnection() *gorm.DB {
    db, err := gorm.Open("mysql", "root:root@tcp(go_db:3306)/api_db?charset=utf8mb4&parseTime=True&loc=Local")
    if err != nil {
        log.Fatalf("Couldn't establish database connection: %s", err)
    }
    return db
}

func main() {
    db := openConnection()
    defer db.Close()
    if err := seeds(db); err != nil {
        fmt.Printf("%+v", err)
        return
    }
}

これで実装できたので動かしてみましょう

$ go run seed.go

できたらmysqlの方にも入って確認してみましょう

$ docker-compose exec db bash
$ mysql -u root -p
$ use api_db;
$ select * from articles;

articlesのDBにもデータが入ってることを確認できたらOKです。

関連記事