LoginSignup
7

More than 5 years have passed since last update.

Golangのgormで、PrimaryKeyにXIDを使う

Last updated at Posted at 2016-07-10

Golangのgormで、PrimaryKeyにUUIDを使う っていうのを以前書いたのですが、MySQLの UUID_SHORT 使ってみると単純にAutoincrementしているだけのようでした。

他にユニークなIDを生成できる仕組みはないかと探していたところ、xid というのを見つけました。

上のやつはどうやらMongo Object IDアルゴリズムをベースにして実装しているようです。サイズもコンパクトでいい感じに見えたので差し替えてみました。

アプリケーションで使えるようにする

Golangで、組み込み型以外の型を使うには、sql driverのinterfaceを実装する必要があります。

  • database/sql.Valuer
  • database/sql.Scanner

実装

  • DBから値を受け取って、Golang側でxid objectに変換
  • SQL Driverが受け取れる値に変換
  • JSONエンコード・デコード処理(API用)

JSONデコードにffjsonを使ってますが、標準のjsonパッケージを使っても問題ないでしょう。

xid.go
package typedef

import (
    "database/sql/driver"
    "errors"

    json "github.com/pquerna/ffjson/ffjson"
    "github.com/rs/xid"
)

// XID redefines a new type to use application.
type XID struct {
    id xid.ID
}

// NewXID creates a new xid.
func NewXID() *XID {
    return &XID{id: xid.New()}
}

// NewXIDFromString creates a XID object to use xid generator.
func NewXIDFromString(str string) *XID {
    id, err := xid.FromString(str)
    if err != nil {
        return nil
    }
    return &XID{id: id}
}

// XIDFromString creates from string representation.
func XIDFromString(str string) (id XID, err error) {
    v, err := xid.FromString(str)
    if err != nil {
        return
    }
    id.id = v
    return
}

// IsZero defines to check whether nil value or not.
func (id XID) IsZero() bool {
    return len(id.id) == 0
}

// EqualString returns both string representation.
func (id XID) EqualString(s string) bool {
    return id.String() == s
}

// String returns xid string representation.
func (id XID) String() string {
    return id.id.String()
}

// Marshalizations

// MarshalJSON returns the encoded JSON string.
func (id XID) MarshalJSON() ([]byte, error) {
    v := id.id
    return []byte(`"` + v.String() + `"`), nil
}

// UnmarshalJSON sets the value that decoded JSON.
func (id *XID) UnmarshalJSON(data []byte) (err error) {
    var v string
    if err = json.Unmarshal(data, &v); err != nil {
        return
    }
    if err = id.Scan(v); err != nil {
        return
    }
    return
}

// Sql driver interface.

// Scan casts a value to xid.ID
// XXX: DBからのvalueは、[]uint8で渡ってくるっぽい(ascii codeが入ってる)
func (id *XID) Scan(value interface{}) (err error) {
    switch v := value.(type) {
    case string:
        id.id, err = xid.FromString(v)
    case []uint8:
        b := make([]byte, len(v))
        for i, a := range v {
            b[i] = byte(a)
        }
        id.id, err = xid.FromString(string(b))
    default:
        err = errors.New("xid.Scan: invalid value")
    }
    return
}

// Value exports the casted object value.
func (id XID) Value() (driver.Value, error) {
    return id.String(), nil
}

Gormモデル定義

XIDをPrimary keyで使うには、タグでカラムの型をきちんと定義する必要があります。

model.go
// Model is a base model that defines a primary key and timestamp on operation.
type Model struct {
    XID       T.XID          `gorm:"type:varchar(20);primary_key" json:"id"` // XXX: XID on create callback.
    CreatedAt T.ResourceTime `json:"createdAt"`
    UpdatedAt T.ResourceTime `json:"updatedAt"`
}

// SoftModel is a base model to delete a record.
type SoftModel struct {
    XID       T.XID          `gorm:"type:varchar(20);primary_key" json:"id"` // XXX: XID on create callback.
    CreatedAt T.ResourceTime `json:"createdAt"`
    UpdatedAt T.ResourceTime `json:"updatedAt"`
    DeletedAt *time.Time     `json:"deletedAt,omitempty"` // XXX: ポインターだとカラム追加できない
}


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

レコード追加時に、XIDを発行し保存

repos.go
package repos

import (
    "github.com/jinzhu/gorm"

    T "bitbucket.org/team1/app/module/typedef"
    "bitbucket.org/team1/app/module/util"
)

// Gorm callbacks

func updateXIDWhenCreate(scope *gorm.Scope) {
    if !scope.HasError() {
        scope.SetColumn("XID", *T.NewXID()) // Primary Key
    }
}

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7