GoのWebアプリケーションで、ORM周りを綺麗に書くTipsです。
今回の例では、ORMにgo-gorpを使ってます。
サービス層からマッパー層使うときに、gorpを直接触りたくないのと、
いちいちerror判定を入れるのが面倒なので、チェインで書けるようにしてみました。
コード一部の記載なので、よしなに参考にしてください。
Before
マッパーがgorpを外に教えちゃうと
mapper.go
// どこかで初期化する
var tx *gorp.Transaction
func GetTransaction *gorp.Transaction {
return tx
}
サービスの全funcで、こんだけのエラー判定ずっと書くのヤバい・・・。
gorpに依存してるのも嫌だ。
service.go
// 例)UserとSessionを作成してSessionを返す
func (s *Service) CreateUserAndSession() (*model.Session, error) {
// *gorp.Transaction
tx, errBegin := mapper.GetTransaction()
if errBegin != nil {
return nil, errBegin
}
u := model.NewUser()
if errIns1 := tx.Insert(u); errIns1 != nil {
return nil, errIns1
}
s := model.NewSession(u.ID)
if errIns2 := tx.Insert(s); errIns2 != nil {
return nil, errIns2
}
if errCommit := tx.Commit().Commit(); errCommit != nil {
return nil, errCommit
}
return s, nil
}
After
マッパーの方で、一枚噛ませるようにして、エラーも内包すると。
mapper.go
package mapper
type MyTransaction struct {
tx *gorp.Transaction
err error
}
func Begin() *MyTransaction {
tx, err = gorp.dbMap.Begin()
return &MyTransaction {
tx: tx,
err: err,
}
}
// CRUDをラップしてチェインできるように
func (t* MyTransaction) Insert(m *interface{}) *MyTransaction {
if t.err == nil {
t.err = t.tx.Insert(m)
}
return t
}
// 最後のコミット時にerrorを返す
func (t* MyTransaction) Commit(m *interface{}) error {
if t.err != nil {
return t.err
}
return t.tx.Commit()
}
サービス側がスッキリ。
error発生しても全てをリレーするため若干パフォーマンス勿体無いですが、
それが常時起こるような構造の方を改善すべきで、コードの保守性のメリットが大きいかなと思います。
service.go
package service
func (s *Service) CreateUserAndSession() (*model.Session, error) {
u := model.NewUser()
s := model.NewSession(u.ID)
if err := mapper.Begin().Insert(u).Insert(s).Commit(); err != nil {
return nil, err
}
return s, nil
}