やりたいこと
- defer関数内のerrorの値を、最終的なerrorの返り値として呼び出し元に返却したい
- 一緒にCommit()とRollback()も上手く捌きたい ≒ 共通化したい
実装例
個人で作成してみた参考レポジトリから、一部抜粋しています。
ファイルを直接参照されたい場合は、こちらからどうぞ。
(if文が続いて読みにくい部分は、各々でリファクタリングしてご参考になればと思います )
tx.go
// トランザクションの共通処理をメソッド化
func (r *Repository) BeginTx(ctx context.Context, db Beginner) (*sql.Tx, func(context.Context, *sql.Tx, error) error, error) {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return nil, nil, apperror.PROCESS_TRANSACTION_FAILED.Wrap(err, err.Error())
}
// commitOrRollback関数を返却する
return tx, commitOrRollback, nil
}
// コミットの実行・コミット失敗時にロールバックする共通処理を関数化
func commitOrRollback(ctx context.Context, tx *sql.Tx, err error) error {
// トランザクション処理が成功している場合
if err == nil {
// コミットを実行
if err := tx.Commit(); err != nil {
// コミット失敗の場合、ロールバックを実施
if rbErr := tx.Rollback(); rbErr != nil {
// ロールバック失敗の場合、その失敗時のエラーを返却
return apperror.PROCESS_TRANSACTION_FAILED.Wrap(err, rbErr.Error())
}
err = apperror.PROCESS_TRANSACTION_FAILED.Wrap(err, err.Error())
log.Printf("[%d]trans: rollbacked sucessfully for handling err: %v",
appcontext.GetTracdID(ctx), err)
// ロールバック成功の場合、コミット失敗時のエラーを返却
return err
}
// コミット成功の場合、nilを返却
return nil
// トランザクション処理が失敗している場合
} else {
// ロールバックを実施
if rbErr := tx.Rollback(); rbErr != nil {
// ロールバック失敗の場合、その失敗時のエラーを返却
return apperror.PROCESS_TRANSACTION_FAILED.Wrap(err, rbErr.Error())
}
log.Printf("[%d]trans: rollbacked sucessfully for handling err: %v",
appcontext.GetTracdID(ctx), err)
// ロールバック成功の場合、トランザクション処理時のエラーを返却
return err
}
}
// トランザクション処理の一例
// 新規ユーザの登録と残高作成を行うトランザクション処理
func (r *Repository) RegisterUserTx(
ctx context.Context, db Beginner, name string,
// 返り値のerrorをerr変数で定義しておく
) (user *entity.User, balance *entity.Balance, err error) {
// トランザクションの共通処理を呼び出し、commitOrRollback関数を受け取る
tx, commitOrRollback, err := r.BeginTx(ctx, db)
defer func() {
// defer関数内でcommitOrRollback関数を呼び出す
// errとは別に、txErr変数を定義・初期化する
// txErrがnilである場合、errの値は上書かれず、そのまま本メソッド(RegisterUserTx)の呼び出し元に返却される
if txErr := commitOrRollback(ctx, tx, err); txErr != nil {
// txErrがnilでない場合、返り値であるerrにtxErrを代入(上書き)する
err = txErr
// 上書かれたこのerrの値が、本メソッド(RegisterUserTx)の呼び出し元に返却される
}
}()
user, err = r.CreateUser(ctx, tx, name)
if err != nil {
// defer関数へ(errを上書く可能性あり)
return nil, nil, err
}
balance, err = r.CreateBalance(ctx, tx, user.ID)
if err != nil {
// defer関数へ(errを上書く可能性あり)
return nil, nil, err
}
// defer関数へ(nilを上書く可能性あり)
return user, balance, nil
}
参考リポジトリ
参考記事
- 「defer関数内のerrorの値を、最終的なerrorの返り値として呼び出し元に返却したい」について参考になった記事
- 「一緒にCommit()とRollback()も上手く捌きたい ≒ 共通化したい」について参考になった記事
追記
-
commitOrRollback
関数のリファクタ版を追記
// commitOrRollback handles committing a transaction or rolling it back in case of an error.
func commitOrRollback(ctx context.Context, tx *sql.Tx, err error) error {
if err != nil {
return rollbackTransaction(ctx, tx, err)
}
return commitTransaction(ctx, tx)
}
// rollbackTransaction handles the rollback of a transaction and logs the appropriate message.
func rollbackTransaction(ctx context.Context, tx *sql.Tx, err error) error {
if rbErr := tx.Rollback(); rbErr != nil {
return apperror.PROCESS_TRANSACTION_FAILED.Wrap(err, rbErr.Error())
}
log.Printf("[%d]trans: rollbacked sucessfully for handling err: %v",
appcontext.GetTracdID(ctx), err)
return err
}
// commitTransaction handles the commit of a transaction and logs the appropriate message.
func commitTransaction(ctx context.Context, tx *sql.Tx) error {
if err := tx.Commit(); err != nil {
return rollbackTransaction(ctx, tx, err)
}
return nil
}