panic() するか non-nil error が返ったらロールバック用の処理を実行するユーティリティ構造体 Guard を作った
こんな感じに使う。
package main import ( "errors" "fmt" ) func main() { fmt.Println("start") if err := f(); err != nil { fmt.Println(err.Error()) } fmt.Println("end") } func f() (err error) { fmt.Println("f() start") g := &Guard{} defer func() { err = g.Rollback(recover()) }() g.Add(func() { fmt.Println("rollback 1") }) // if true { // panic("panic!") // } g.Add(func() { fmt.Println("rollback 2") }) // oops, invoke all rollback functions in reverse order... e := errors.New("something went wrong") if e != nil { err = g.Rollback(e) err = g.Rollback(e) // this won't call rollback functions twice! return } fmt.Println("f() end") return } // Guard invokes "rollback functions" if Rollback method received non-nil value // (e.g. recover(), non-nil error). // // TODO: use mutex or something if the methods may be invoked in parallel! type Guard struct { // rollback functions rbFuncs []func() } // Rollback rolls back if v is non-nil. // // defer func() { err = g.Rollback(recover()) }() // // // or // // if e != nil { // err = g.Rollback(e) // err = g.Rollback(e) // this won't call rollback functions twice! // return // } func (g *Guard) Rollback(v interface{}) error { var err error if e, ok := v.(error); ok { err = e } else if v != nil { err = fmt.Errorf("%s", v) } if err != nil { g.RollbackForcefully() } return err } // RollbackForcefully calls rollback functions in reverse order. func (g *Guard) RollbackForcefully() { // Call rollback functions in reverse order for i := len(g.rbFuncs) - 1; i >= 0; i-- { g.rbFuncs[i]() } // Do not rollback twice g.rbFuncs = nil } // Add adds given rollback functions. func (g *Guard) Add(f func()) { g.rbFuncs = append(g.rbFuncs, f) }
そんなに行数無いので管理面倒になるしリポジトリは作ってない。 とりあえず Volt でちょうどこんな interface を実装した struct が沢山あって (インタプリタ的な DSL を実装中で、Value は全てのベースとなる型)
type Op interface { // ... Execute(ctx context.Context, args []Value) (ret Value, rollback func(), err error) }
いちいちロールバック用の関数のスライス作って error や panic() の処理して… ってするのはかなり面倒になりそうだったので、構造体にまとめた。 本来の処理に注力できて良い感じに関心の分離ができてる感じ。
ちなみに Volt ではちょっと変更したバージョンを使ってて、
Rollback()
で error を返す所を pkg/errors の Wrap()
使うように変えてる。
エラーメッセージが分かりやすくなっていい感じになった (はず…確認できてないけど)。