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 に合わせることができました。

次はマイグレーションと合わせた運用についてまとめてみようと思います。