Golangのgormで、PrimaryKeyにUUIDを使う


gormという、ORMライブラリを使っています。
タグでprimarykeyやカラムの定義できるし、論理削除もできるすぐれものですが、
Primarykeyの型をintなどにしてしまうと、Auto increment固定なのが困るところです。

```
// gorm.Model
//type Model struct {
//    ID        uint64 `gorm:"primary_key"`
//    CreatedAt time.Time
//    UpdatedAt time.Time
//    DeletedAt *time.Time
//}

type User struct {
    gorm.Model
    Name string
}
```

DBに保存された順番をIDとして採番したくなくて、UUIDやTwitterのSnowflakeとかいろいろ調べたのですが、UUIDでは128ビット必要で長すぎ、別のミドルウエアが必要になるものは使いたくないので、いろいろ調べた結果たどり着いたのが、MySQLの `UUID_SHORT()` 関数です。

やりたいことは単純に下記の3つでした。

* レコードを追加するときにIDを発行
* DBに格納するときは、数値で格納(UUID_SHORTそのままの値)
* (おまけ)クライアントにデータを返すときには、IDは16進数の文字列に変換し縮めて返す。

## UUID独自のタイプを定義

Golangでは、`type` キーワードを使って組み込み型を再定義?できるので、`UUID`という独自タイプを定義します。
DBに格納するときは、`uint64` なんですが、クライアントがアクセスするURLや返すデータとしては 16進数の文字列に変換して文字数を縮めたいので、シリアライズの処理もメソッドとして定義します。

```uuid.go
package typedef

import (
	"errors"
	"strconv"
)

// UUIDは、RDBではuint64で保存するが、
// APIのレスポンスやRedisでは16進数の文字列に変換して返すためのType
type UUID uint64

func (u UUID) IsZero() bool {
	return u == 0
}

func (u UUID) MarshalJSON() ([]byte, error) {
	hex := UintToHex(u)
	if hex != "" {
		return []byte(`"` + UintToHex(u) + `"`), nil
	}
	return nil, errors.New("UUID.MarshalText: invalid value")
}

// TODO: 多分タブルクオートを先にトリミングする必要がある
func (u *UUID) UnmarshalJSON(data []byte) (err error) {
	i := UUID(UintFromHex(string(data)))
	u = &i
	return
}

func (u UUID) MarshalText() ([]byte, error) {
	hex := UintToHex(u)
	if hex != "" {
		return []byte(UintToHex(u)), nil
	}
	return nil, errors.New("UUID.MarshalText: invalid value")
}

func (u *UUID) UnmarshalText(data []byte) (err error) {
	i := UUID(UintFromHex(string(data)))
	u = &i
	return
}

func (u UUID) ToHex() string {
	return strconv.FormatUint(uint64(u), 16)
}

// MARK: Helpers

func UintToHex(i interface{}) string {
	switch v := i.(type) {
	case uint64:
		return strconv.FormatUint(v, 16)
	case UUID:
		return strconv.FormatUint(uint64(v), 16)
	}
	return ""
}

func UintFromHex(hex string) uint64 {
	if num, err := strconv.ParseUint(hex, 16, 64); err != nil {
		return 0
	} else {
		return num
	}
}
```

## モデル定義の構造体を作る

Golangでは構造体に構造体を埋め込めるらしいので、IDのカラムを定義したベースのモデルの構造体を作りました。
IDだけ持ったもの、時間付きのもの、論理削除用(`gorm`で定義されているものとほぼ同じもの)の3種類作りました。


```model.go
type ModelUUID struct {
	UUID UUID `gorm:"primary_key"` // XXX: UUID_SHORT() on create callback.
}

type Model struct {
	UUID      UUID      `gorm:"primary_key"` // XXX: UUID_SHORT() on create callback.
	CreatedAt time.Time `json:"createdAt"`
	UpdatedAt time.Time `json:"updatedAt"`
}

type SoftModel struct {
	UUID      UUID       `gorm:"primary_key"` // XXX: UUID_SHORT() on create callback.
	CreatedAt time.Time  `json:"createdAt"`
	UpdatedAt time.Time  `json:"updatedAt"`
	DeletedAt *time.Time `json:"deletedAt,omitempty"`
}
```

実際に使うモデルに上の構造体を埋め込んで使います。

```user.go
type User struct {
	//gorm.Model        // see: https://github.com/jinzhu/gorm#conventions
	SoftModel
	Username       string          `sql:"size:180;not null;index" json:"username"`
```


## レコード追加時にUUIDを発行しDBに保存する。

`gorm`では、新規作成、更新、削除時などにコールバックを使えるので、それを利用します。
リレーションオブジェクトが保存される前だったらどこでもよいと思うのですが、モデルのバリデーションが実行されたあとにしました。
`UUID_SHORT()` 関数の実行結果を取得して、PrimaryKeyのカラムにセットします。

```model.go
var (
	reqKey        private
	UUIDGenerator = func(db *gorm.DB) UUID {
		var uuid uint64
		db.Unscoped().Raw("SELECT UUID_SHORT()").Row().Scan(&uuid)
		return UUID(uuid)
	}
)

// MARK: Gorm callbacks

func UpdateUUIDWhenCreate(scope *gorm.Scope) {
	if !scope.HasError() {
		scope.SetColumn("UUID", UUIDGenerator(scope.DB())) // Primary Key
	}
}

func init() {
	// XXX: DBのtimestampをUTCにする
	gorm.NowFunc = util.NowUtcFunc
gorm.DefaultCallback.Create().Before("gorm:save_before_associations").Register("myapp:update_uuid_when_create", UpdateUUIDWhenCreate)
}
```


こんな感じで、DBに格納された順じゃないIDを発行することができました。

`UUID_SHORT()` の仕様は下記を参考にしてください。

http://mysql.stu.edu.tw/doc/refman/5.1-olh/ja/miscellaneous-functions.html#function_uuid-short