go实现业务回滚(defer妙用之一)

go实现业务回滚(defer妙用之一)

最近在用GO重写本组的OSS系统,需要实现业务的回滚功能(即一个流程中,先与A进行交互,然后再与B进行交互,如果B失败了,回滚与A交互的逻辑这样)。另外,在初始化的时候也经常需要在任何一个模块初始化失败的时候回滚其他模块

太长不看版

defer是延时调用函数,在函数退出之时会进行调用,而且defer函数的调用顺序也是先调用的后执行,故defer其功能上就比较适合进行回滚。

总体思路便是一个初始化函数执行ok后,使用defer对应其回退函数,回退函数中会对error进行判断,若error不为空,则说明是异常退出,进行回退;若error是空,则说明是正常退出函数,不需要进行回退。(这部分逻辑在闭包函数中实现)

这里以函数的初始化作为demo

package main

import "fmt"
import "errors"

func helloWolrd() (err error) {
    rollback := func(rollback func()) {
        if err != nil {
            rollback()
        }
    }

    err = initA()
    if err != nil {
        return err
    }
    defer rollback(rollbackInitA)


    err = initB()
    if err != nil {
        return err
    }
    defer rollback(rollbackInitB)

    err = initC()
    if err != nil {
        return err
    }
    defer rollback(rollbackInitC)

    return nil
}

func main(){
    helloWolrd()
}

func initA() (err error) {
    fmt.Println("init A")
    return nil
}

func initB() (err error) {
    fmt.Println("init B")
    return nil
}

func initC() (err error) {
    fmt.Println("init C")
    return errors.New("roll back test")
}

func rollbackInitA() {
    fmt.Println("rollback Init A")
}

func rollbackInitB() {
    fmt.Println("rollback Init B")
}

func rollbackInitC() {
    fmt.Println("rollback Init C")
}

代码解释:

  1. rollback以及defer rollback(xx),会对error进行判断,若error不为空,则说明是异常退出,进行回退;若error是空,则说明是正常退出函数,不需要进行回退
  2. 代码细节问题:defer以及error判断,是先判断error,再使用defer延时释放。
  3. 这种方式也会有局限性:依赖于函数返回值error,不能乱修改它;如果回滚函数需要不一样的入参,则需要声明多个rollback:rollback1,rollback2…..

程序输出

feiqianyousadeMacBook-Pro:go yousa$ ./hello 
init A
init B
init C
rollback Init B
rollback Init A

太长版

业务回退在实现的时候,先列一个比较初级的回退方法,很麻烦

1.错误处理时,手动逆序调用之前的初始化模块的初始化回退函数

package main

import "fmt"
import "errors"

func helloWolrd() (err error) {
    err = initA()
    if err != nil {
        return err
    }

    err = initB()
    if err != nil {
        rollbackInitA()
        return err
    }   
    err = initC()
    if err != nil {
        rollbackInitB()
        rollbackInitA()
        return err
    }

    return nil
}

//...其他太长略掉

2.GOTO版本

GOTO这种版本比较像C的风格,整体来看除了会发生较多跳转,代码比较整洁

func helloWolrd() (err error) {
    err = initA()
    if err != nil {
        goto ROLLBACK_A
    }

    err = initB()
    if err != nil {
        goto ROLLBACK_B
    }   
    err = initC()
    if err != nil {
        goto ROLLBACK_C
    }

    return nil

ROLLBACK_C:
    rollbackInitB()

ROLLBACK_B:
    rollbackInitA()

ROLLBACK_A:
    return err
}

使用goto明显缺点:

不能随便使用:=,尽量建议在函数开始处声明变量,否则容易报错”goto xx jumps over declaration of”

比如将代码修改到如下:

func helloWolrd() (err error) {
    err = initA()
    if err != nil {
        goto ROLLBACK_A
    }

    err = initB()
    if err != nil {
        goto ROLLBACK_B
    }   
    err = initC()
    if err != nil {
        goto ROLLBACK_C
    }

    hello := "hello"
    fmt.Println(hello)

    return nil

ROLLBACK_C:
    rollbackInitB()

ROLLBACK_B:
    rollbackInitA()

ROLLBACK_A:
    return err
}

//报错
feiqianyousadeMacBook-Pro:go yousa$ go build hello.go 
# command-line-arguments
./hello.go:40: goto ROLLBACK_A jumps over declaration of hello at ./hello.go:52
./hello.go:45: goto ROLLBACK_B jumps over declaration of hello at ./hello.go:52
./hello.go:49: goto ROLLBACK_C jumps over declaration of hello at ./hello.go:52

当然也可以尝试定义一个函数数组,在循环里调用函数,若出错则循环调用回滚函数这种方式。但总觉得实现起来比较麻烦,不容易维护。

结语

本文主要介绍了自己在使用go实现业务回滚时首先从“手动逆序调用之前的初始化模块的初始化回退函数”到“goto跳转回滚”再到“使用defer调用回滚函数”三种实现回滚功能的写法

如果有更好的写法欢迎交流

猜你喜欢

转载自blog.csdn.net/qq_15437667/article/details/78542860