当前的很多都是简单粗暴,begin,然后业务,再commit, rollback
这只是其中一种最直接的玩法,相信大家都是八仙过海,能各显神通,比如通过defer做commit或者rollback,其实都没太大问题,都能基本保证功能完成,但是总感觉不太丝滑,
这里罗列几个问题:
- 比如直接简单粗暴的,就会在每个函数返回点要rollback或者commit,好一点的就加一个defer统一处理
- 千人千面,每个人用法都会不同,虽然可能没太大问题,但是代码迥异,风格各异,看着不舒服,维护起来也麻烦
- 每个人的用法处理起来可能都会有一点的缺失,考虑不周全的地方,但是大家又都各自瞧不上,哈哈哈
基于上述问题,是不是可以考虑统一一个入口来完成db事务,让我们写业务的时候基本无感知,解耦这些rollback和commit呢?
这里抛块砖,看看我个人认为写起来比较丝滑的方式:
这样做的好处有几点:
- 在打包事务的处理函数transHandler里,取它的db来用,遇到失败直接返回err就可以了,完全不感知事务
- 统一处理事务,有相当于做了个勾子,方便统一管理和规范处理,不再是各自写各自的事务了。
- 在transHandler函数里面可以肆意妄为,比如一段业务:先存一个记录到数据库,得到 Id 后要用它通知其他服务,然后再做下一个持久化,在transHandler就像没有事务的踪迹一样用
有了以上这几个优点,写起事务来让人感觉就很巴适,丝滑得很。后来我在我们的框架基础上做了个尝试实现,实现了,但是废了比较大劲,改动了一些地方,所以只能说作为一个参考,并对上述丝滑方式的做一个总结,可以有兴趣的同学可以根据这个启发在自己的框架上实现
// WithTransCommit 通过事务方式执行业务函数(通过ctx解耦),参数为函数func(ctx context.Context) error, error不为nil时回滚 func WithTransCommit(execFunc func(ctx context.Context) error) (err error) { transDB := WriteDBPaaS.Begin() // 这里使用db实例启动一个事务db,稍后会注入到需要执行的函数中作为参数 defer func() { if errRecover := recover(); errRecover != nil { err = errors.New(fmt.Sprintf("panic导致事务失败: %v", errRecover)) rollBackHandler(transDB) } }() // 通过ctx解耦,业务需要通过ctx获取事务db实例 ctx := context.WithValue(context.Background(), PaasDB, transDB) // 这里执行业务函数 err = execFunc(ctx) if err != nil { err = errors.Wrap(err, "任务执行失败") rollBackHandler(transDB) return } err = transDB.Commit().Error if err != nil { err = errors.Wrap(err, "提交失败") rollBackHandler(transDB) return } return } // 使用 func demo(){ err = cmn.WithTransCommit(func(ctx context.Context) (err error) { err = service.NewServiceRepoWithDB(ctx).Add(ser) if err != nil { return } var appclusters []iservice.AppCluster appclusters = append(appclusters, iservice.AppCluster{ AppId: ser.Id, ClusterId: 4, ClusterName: "default", GroupName: "pm", }) err = service.NewAppClusterRepoWithDB(ctx).AddList(appclusters) if err != nil { return } panic("模拟业务panic") return }) // handle err } |
总结:
- 通过一个函数func(ctx/gorm.db)err ,作为一个handler,打包所有业务,作为一个处理器,处理器的参数是ctx(带有db的ctx,用ctx是为了解耦仓储层)
- 业务逻辑中,需要处理持久化时要用handler的ctx/gorm.db作为存储使用的db实例
- 实现一个事务执行函数
其实基于我们现有代码框架下,实现过程中还是遇到比较多问题,比如解耦,改变仓储层耦合db的使用方式(改为传参获取db)。。。 但总体来说我觉得是值得的,能让我们的代码看上去优雅简洁
实现的方式可以灵活多变,整体要解决的问题就上述罗列的问题,思路核心也就是一个handler打包事务相关的业务逻辑, 一个doTrans函数发起事务,执行事务的commit或rollback