Golang: xo + sqlx
2019.04.30
Golangでデータベースを操作するためにxoとsqlxを併用する方法について。
Golangでデータベースを操作する場合はテーブルをStructに対応付けることが多いと思います。
しかしデータベースも必要に応じて構造を更新していくことになるため、コードをその時々の構造に追従するのは人の手で行うのは意外と面倒です(なによりミスも起きやすくなる)。
そこでデータベースに合わせてコードを生成することができる xo と標準ライブラリの database/sql
の拡張と謳う sqlx を使うことでこのあたりの運用を簡単にしてみようと思います。
前提として以下の環境が構築済みとしています。
PostgreSQL
バージョンは9.6、以下のDDLで users
テーブルを作成済み。
CREATE TABLE users (
user_id bigserial PRIMARY KEY,
email varchar(100) NOT NULL,
created_at timestamp NOT NULL,
updated_at timestamp NOT NULL
);
Golang
Golangのバージョンは1.12で xo がインストール済み。
xoについて
READMEに従って以下のコマンドを実行すると、 models
以下にファイルが出力されます。
# generate code for a postgres schema
$ xo pgsql://user:pass@host/dbname -o models
上述の users
テーブルは models/user.xo.go
として以下のように出力されます。
// Package models contains the types for schema 'public'.
package models
// Code generated by xo. DO NOT EDIT.
import (
"errors"
"time"
)
// User represents a row from 'public.users'.
type User struct {
UserID int64 `json:"user_id"` // user_id
Email string `json:"email"` // email
CreatedAt time.Time `json:"created_at"` // created_at
UpdatedAt time.Time `json:"updated_at"` // updated_at
// xo fields
_exists, _deleted bool
}
// 以下略
データベースのマイグレーションと合わせて xo でモデル定義を更新することで、データベースとコードの構造の乖離を防ぐことができます。
sqlxについて
sqlx の使い方自体はREADMEなどを見てもらうとして、以下のページではStructのタグについての記述があります。
You can use the db struct tag to specify which column name maps to each struct field, or set a new default mapping with db.MapperFunc().
これについては必須というわけではないようですが、明示的にタグでカラムを指定するほうが望ましいでしょう。
xo + sqlx
xo で出力されるStructのタグは json
のみなので、 sqlx に対応させるために db
タグを追加してみます。
出力をカスタマイズするにはREADMEにあるように、テンプレートファイルを用意することになります。
もととなるテンプレートを templates
にコピーしたら必要な箇所を編集し、それらのテンプレートを使用することができます。
# change to working project directory
$ cd $GOPATH/src/path/to/my/project
# create a template directory
$ mkdir -p templates
# copy xo templates for postgres
$ cp "$GOPATH/src/github.com/xo/xo/templates/*" templates/
# remove xo binary data
$ rm templates/*.go
今回はPostgreSQLの出力にタグを追加したいので postgres.type.go.tpl
を編集します。
編集するのは10行目のタグを記述している箇所、ここに db:"{{ .Col.ColumnName }}"
を追記します。
- {{ .Name }} {{ retype .Type }} `json:"{{ .Col.ColumnName }}"` // {{ .Col.ColumnName }}
+ {{ .Name }} {{ retype .Type }} `json:"{{ .Col.ColumnName }}" db:"{{ .Col.ColumnName }}"` // {{ .Col.ColumnName }}
オプションでテンプレートを指定することでカスタマイズしたモデル定義を出力できます。
$ xo pgsql://user:pass@localhost/dbname -o models --template-path templates/
これにより出力される user.xo.go
は以下、タグに db
が追加されるようになりました。
// Package models contains the types for schema 'public'.
package models
// Code generated by xo. DO NOT EDIT.
import (
"errors"
"time"
)
// User represents a row from 'public.users'.
type User struct {
UserID int64 `json:"user_id" db:"user_id"` // user_id
Email string `json:"email" db:"email"` // email
CreatedAt time.Time `json:"created_at" db:"created_at"` // created_at
UpdatedAt time.Time `json:"updated_at" db:"updated_at"` // updated_at
// xo fields
_exists, _deleted bool
}
// 以下略
これで xo で出力したモデル定義を sqlx に合わせることができました。
次はマイグレーションと合わせた運用についてまとめてみようと思います。