Techtouch Developers Blog

テックタッチ株式会社の開発チームによるテックブログです

GORM v2 触ってみた Breaking Changes 編

adventCalendar-day17

この記事はテックタッチアドベントカレンダー 17 日目の記事です。

SRE チームの taisa です。韓国ドラマの「スタートアップ」が最近 Netflix で最終話までみれるようになりましたね。最近はぼちぼち来年の抱負を考えるようになりました。懲りずにまた英語をやろうかと考え中です。

今回は「GORM v2 触ってみた Major Features 編」の続きで「Breaking Changes 編」です。

GORM v2 リリースノート

1 系から移行する場合は少なくともこちらにある内容の対応をする必要があります。

gorm.io

Breaking Changes

1 系からの変更で影響が大きそうな変更内容を確認します。

  • Tags
  • Soft Delete
  • BlockGlobalUpdate
  • ErrRecordNotFound
  • Updating with struct
  • Migrator
  • Count
  • Transaction

順に見ていきます。

Tags

auto_increment などのタグ名は camelCase 推奨で snake_case は使えなくなります。確認したところ現時点では snake_case でも動作しました。

type User struct {
    ID         int `gorm:"primaryKey"` // camelCase とする
    CompanyID  int
    Company    Company
    Name       string
    Address    string
    Age        int
}

Soft Delete

DeletedAtgorm.DeletedAtを指定するようになりました。かわりに、gorm.DeletedAtを指定していればカラム名がDeletedAtでなくても Soft Delete となります。確認したところ*time.Time と指定しても Soft Delete とならなかったので該当箇所はすべて書き換える必要があります。

type User struct {
  ID        uint
  DeletedAt gorm.DeletedAt
}

type User struct {
  ID      uint
  // 違うカラム名でもOK
  Deleted gorm.DeletedAt
}

BlockGlobalUpdate

BlockGlobalUpdateモードがデフォルトとなり、DELETE FROM usersのような Where 句の指定がない場合は SQL が実行されません。実行したい場合は意図的にAllowGlobalUpdateモードにする必要があります。意図しない更新が防げるのでうれしいアップデート。

// Where 句の指定があれば実行可能
db.Where("name = ?", "username").Delete(&User{})

// 実行されない
db.Raw("delete from users")

// `AllowGlobalUpdate: true`を指定すると実行可能
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})

ErrRecordNotFound

RecordNotFoundIsRecordNotFoundError関数がなくなったので以下のような書き方でチェックする必要があります。また、FirstLastTakeを利用したときにErrRecordNotFoundエラーが返る可能性があります。

err := db.First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
    return gorm.ErrRecordNotFound
}

v1 で以下のような書き方にて Not Found チェックしている場合は変更する必要があります。

result := db.First(&user)
if result.RecordNotFound() {
    return gorm.ErrRecordNotFound
}
err = db.First(&user).Error
if gorm.IsRecordNotFoundError(err) {
    return gorm.ErrRecordNotFound
}

Updating with struct

構造体やマップを使って更新する際に、Selectで指定したカラムだけを更新できるようになりました。また、ゼロバリュー(0 や空)の値でもその値に更新できます。ドキュメントの例は構造体でしたが、構造体を使うとPanicが発生して原因の特定はできませんでした(ドキュメントのUpdateUpdatesの誤りだと思われます)。

db.Model(&model.User{}).
    Select("Age").
    Where("name = ?", "username").
    Updates(map[string]interface{}{"Name": "hello", "Age": 0, "Address": ""})
// UPDATE `users` SET `age`=0 WHERE name = 'username'

マップを使って Where 句にゼロバリューを入れてみても値が消えず、指定した値で検索できました。

user := model.User{}
db.Model(&model.User{}).
    Where(map[string]interface{}{"Age": 0}).First(&user)
// SELECT * FROM `users` WHERE `Age` = 0 ORDER BY `users`.`id` LIMIT 1

Migrator

Migrator はいくつかの変更がありますが、一番影響ありそうな変更はデフォルトで外部キーがはられるようになったことです。

  • Migrator will create database foreign keys by default

GORM 2.0 Release Note | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

Count

地味ですが、Count の引数は*int64だけとなりました。これまでほかの型を使っている場合は変更する必要があります。

Transactions

Transactionを使うことが推奨となり、RollbackUnlessCommittedなどいくつかのドランザクション関数が廃止になりました。これも利用している箇所があれば書き換える必要があります。

以上、影響の 1 系から 2 系にアップデートするにあたり影響の大きそうな変更の紹介でした。ほかにもいくつか取り上げていない変更があるのでリリースノート公式ドキュメントを参考にしてみてください。

まとめ

2 回に渡って(Prometheus を入れると 3 回)GORM v2 の変更点を実際に動かしながら確認していきました。すごく魅力的な機能がある一方でサンプルコードに誤りがあったり動かなかったり、移行するにしてももう少し安定してからの方が良さそうだな、という印象を受けました。ドキュメントなど今回触ってみて気になった箇所は PR を出して少しでも貢献しようと思います。

次回は、kosy による「(仮)Material-ui を使ってドラッグ&ドロップで並べ替えできる Table を作る」です!よろしくです!