こちらの続きです。
今回は GORM を使ってデータベース操作とマイグレーションを行う方法について書きます。
GORM は Golang 用の ORM です。
公式ドキュメントがかなり細かく書かれているので、使い方で詰まってもそこを見れば大体解決できます。
マイグレーション機能についても提供されているものの、カラム削除ができないといった制限もあります。
そのため、他言語やフレームワークでよくあるファイルベースのマイグレーションとして sql-migrate を使う例についても紹介します(実際開発中のプロダクトではこちらを使っています)。
利用するリポジトリ
前回に引き続き以下を利用します。
何をやっているのか
前述のとおり公式ドキュメントを見れば使い方は大体わかるので、ここでは最低限の紹介にとどめます。
GORM は ORM なので、構造体とデータのマッピングや CRUD の実行など、DB 操作は一通りできます。
type userRepository struct{}
// 検索の例
// First にポインタを渡すことでデータをマッピングできる
func (r userRepository) Find(id uint, u *entity.User) error {
return dbManager.Where(&entity.User{ID: id}).First(u).Error
}
// 挿入の例
func (r userRepository) Create(u *entity.User) error {
return dbManager.Create(u).Error
}
また、構造体のフィールドとカラムのマッピングには gorm タグを使います。
// ユーザデータを保持するための構造体
type User struct {
ID uint `gorm:"primary_key" json:"id"`
Name string `gorm:"column:name,type:varchar(20);not null"` // column で明示しない場合はフィールド名をスネークケースに変えたものをカラムとして使う
Tokens []Token // 関連データもマッピング可能
}
// テーブル名については、構造体を複数形にしたものをデフォルトで使う
// 構造体に TableName 関数を追加することで上書き可能
func (User) TableName() string {
return "my_users"
}
マイグレーションについては AutoMigrate 関数をコールすることで実行してくれます。
// app/infrastructure/database/database.go
// コードは抜粋です
// DB に接続
dbManager, err = gorm.Open(mysql.New(mysql.Config{
DSN: dsn,
}), &gorm.Config{
Logger: logger.Default.LogMode(logMode),
})
// マイグレーション実行
// 実行したい構造体を列挙していく
if err := dbManager.AutoMigrate(entity.Token{}, entity.User{}); err != nil {
return err
}
sql-migrate を使う場合、Dockerfile の以下コードのコメントアウトをはずす必要があります。
RUN go install github.com/rubenv/sql-migrate/...@v1.1.1
sql-migrate を使ったマイグレーションについては上記リポジトリにコードが無いので、簡単に使い方を説明しておきます。
基本的なコマンドは以下のとおりです。
$ sql-migrate new {マイグレーション名} # タイムスタンプ+マイグレーション名をファイル名にしてファイルが生成される
$ sql-migrate up # マイグレーション実行
$ sql-migrate down # ロールバック
$ sql-migrate up -env test # 環境を指定して実行(デフォルトは development)
まず、DB への接続情報を定義した yaml ファイルを用意します。
変数も利用可能なので、秘匿情報をべた書きしなくて済みます。
# dbconfig.yml
# 開発用
development:
dialect: mysql
dir: db/migrations
datasource: ${DB_USER}:@tcp(${DB_HOST})/${DB_NAME}?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Asia%2FTokyo
table: migrations
# テスト用
test:
dialect: sqlite3
datasource: test.db
dir: db/migrations_sqlite3
# 本番用
production:
dialect: mysql
dir: db/migrations
datasource: ${DB_USER}:${DB_PASSWORD}@tcp(${DB_HOST})/${DB_NAME}?charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Asia%2FTokyo
table: migrations
各環境の設定で対象ディレクトリを指定できるので、(例えば RDBMS の種類が違うなどで)環境ごとにマイグレーションファイルを変えるといったことも可能です。
マイグレーションファイルは SQL ファイルで書き、+migrate up/down
というコメントで実行するクエリを判断します。
-- +migrate Up
create table if not exists `users` (
`id` int unsigned not null auto_increment,
`created_at` datetime not null default current_timestamp,
`updated_at` datetime on update current_timestamp,
`name` varchar(20) not null comment '名前',
primary key(id)
) comment='ユーザ'; -- セミコロンで区切れば、一度のマイグレーションで複数のクエリを実行可能
-- +migrate Down
drop table if exists `users`;
最後に、前述のコマンドを実行してマイグレーションを実行します。
まとめ
今回はデータベースまわりについてざっくり説明しました。
GORM を最初に使ったのは 2017 年ごろだったと思いますが、当時から色々なことができて感動した記憶があります。
DB 操作は GORM だけで完結できますが、今回紹介したように特定の分野だけ別ツールを使うといったことも可能なので、プロダクトに合った組み合わせを探すのが良いと思います。
次回は CI について書きます。
※2022/5/7追記
こちらにて続編を書きました。