post Image
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


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。