一个非侵入的Go事务管理库——如何使用
在文章"清晰架构(Clean Architecture)的Go微服务: 事物管理"中,我谈到了如何在清晰架构中实现非侵入的事务管理。
它允许你把事务代码与业务逻辑代码分开,并且让你在编写业务逻辑时不必考虑事务。但它也有一些缺点。首先,它是整个清晰框架(Clean Architecture)的一部分,所以你不能抛开框架单独使用它。其次,尽管它对业务逻辑没有侵入,但它对框架有侵入。你需要修改框架的各个层,使其工作,这使他看起来比较复杂。 第三,正如我在文章中提到的,它存在一个依赖泄漏的漏洞。我虽然在文章中指出了解决方案,但它是一个比较大的改动,因此我当时就把它先放下了。现在,我终于有时间重新拾起它,并做了重要改进,结果令人非常满意。
项目需求
以下是新的项目需求:
- 把事务管理代码写成一个单独的第三方库,这样人们就可以在任何框架中使用它。
- 使其对框架无侵入,这意味着除了在用例层之外,在清晰架构的任何层中都没有事务代码。几乎所有的事务代码都在第三方的事务库中。
- 修复以前设计中的依赖泄漏。
最终,我完成了所有的目标,结果出乎意料的好。我将写两篇文章来描述它,这篇文章讨论如何使用这个第三方库,下一篇文章讨论事务管理库的工作原理。当你要在应用程序里使用事务管理库时,你的程序分成了两部分。一部分是第三方库的程序,另一部分是应用程序的代码(它需要调用事务管理库中的函数)。本文只讲应用程序代码。
如何在项目中使用事务管理库
要想让业务函数支持事务,需要做两件事。首先,创建数据库链接;其次,使用创建的数据库链接运行SQL语句。我假设你在项目中使用了清晰架构。在这种情况下,“创建数据库链接”会在应用程序容器((详情参见"清晰架构(Clean Architecture)的Go微服务: 程序容器(Application Container)" )中完成,“运行SQL语句”会在业务逻辑(数据持久层)中完成。如果没有使用清晰架构,你可能会使用某种非常类似的分层架构,结构还是一样。如果你没有使用任何框架或分层架构,那么这两种代码可能会在一个地方。
你可能想知道它与没有事务支持的代码有什么不同?几乎没有。不管有没有事务支持,应用程序都要编写相同的代码,事务管理库会在后端处理所有事情。
本文中的所有代码都在"jfeng45/servicetmpl1"中,这是一个能自我进化的微服务框架,它提供了如何使用事务库的例子。
创建数据库链接
创建数据库链接有两种不同的方法,使用哪种方法取决于是否需要缓存数据库链接。
获取数据库链接
下面是创建数据库链接的代码。它在"sqlFactory.go"文件中。因为清晰架构使用了工厂方法模式(factory method pattern),这里的代码是它的一部分。如果你不想使用工厂方法模式,也是一点问题都没有的。因为数据库链接在架构中是要被缓存的,所以框架代码需要调用事务库中的函数“BuildSqlDB()”,它首先检查数据库链接是否已经存在。如果没有,则调用“factory.BuildSqlDB(&tdbc)”来创建一个。在此之前,它获取需要的参数并将它们保存在“DatabaseConfig”中,“DatabaseConfig”也是在事务库中定义的。之后它调用内部函数“buildGdbc()”生成合适的“gdbc.SqlGdbc"接口(要根据你是否需要事务)。最后,检查如果数据库链接不在缓存中,则把它放入缓存。
// implement Build method for SQL database
func (sf *sqlFactory) Build(c container.Container, dsc *config.DataStoreConfig) (DataStoreInterface, error) {
logger.Log.Debug("sqlFactory")
key := dsc.Code
//if it is already in container, return
if value, found := c.Get(key); found {
logger.Log.Debug("found db in container for key:", key)
sdb := value.(*sql.DB)
return buildGdbc(sdb, dsc.Tx)
}
tdbc :=databaseConfig.DatabaseConfig{dsc.DriverName, dsc.UrlAddress, dsc.Tx}
db, err := factory.BuildSqlDB(&tdbc)
if err != nil {
return nil, err
}
gdbc, err := buildGdbc(db, dsc.Tx)
if err != nil {
return nil, err
}
c.Put(key, gdbc)
return gdbc, nil
}
下面是创建“SqlGdbc”接口的内部函数。"SqlGdbc"接口有两种实现,一种是"SqlConnTx",它支持事务。另一个是“SqlDBTx”,它不支持事务。
func buildGdbc(sdb *sql.DB,tx bool) (gdbc.SqlGdbc, error){
var sdt gdbc.SqlGdbc
if tx {
tx, err := sdb.Begin()
if err != nil {
return nil, err
}
sdt = &gdbc.SqlConnTx{DB: tx}
logger.Log.Debug("buildGdbc(), create TX:")
} else {
sdt = &gdbc.SqlDBTx{sdb}
logger.Log.Debug("buildGdbc(), create DB:")
}
return sdt, nil
}
有一种更简单的方法可以直接从事务库中获得"SqlGdbc",函数是"factory.Build()"。但是当使用它时,你不能缓存数据库链接,所以我没有在框架中使用它。但是如果你不需要缓存数据库链接,调用“factory.Build()”是一个更好的方法。
数据库配置参数
数据库配置参数是在第三方事务库中定义的,但数据本身是保存在业务项目中。应用程序首先需要组装参数并将它们传递给事务库,以便得到合适的数据库链接。在我们的框架中,包括数据库参数在内的所有应用程序配置数据都保存在一个文件中。框架代码将负责从文件中获取数据。你如果不想将参数保存在文件中,直接将参数写成程序中的硬编码传递给事务库更容易。
下面是配置文件“appConfigDev.yaml”中的部分代码。对于数据库来说,关键是如何让事务库知道需要的是事务链接还是非事务链接。它有多种办法可以完成。例如,你可以为每个函数设置一个事务标志,但这需要改动大量的代码。我发现最简单的方法是将所有支持事务的函数放在一个特殊的用例(Use Case)中。在下面的示例中,有三个用例:“registration”、“listUser”和“registrationTx”,其中只有“registrationTx”是支持事务的,因为它使用“*sqlConfigTx”作为“dataStoreConfig”。
useCaseConfig:
registration:
code: registration
userDataConfig: &userDataConfig
code: userData
dataStoreConfig: *sqlConfig
listUser:
code: listUser
userDataConfig: *userDataConfig
cacheDataConfig: &cacheDataConfig
code: cacheData
dataStoreConfig: *cacheGrpcConfig
registrationTx:
code: registrationTx
userDataConfig: &userDataConfigTx
code: userData
dataStoreConfig: *sqlConfigTx
下面是来自同一配置文件的部分代码。可以看到,在“sqlConfigTx”中,有一个参数“tx:ture”,它表明它是支持事务的。
sqlConfig: &sqlConfig
code: sqldb
driverName: mysql
urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8"
dbName:
tx: false
sqlConfigTx: &sqlConfigTx
code: sqldb
driverName: mysql
urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8"
dbName:
tx: true
在业务逻辑中访问数据库
我们用一个业务函数做例子,来展示支持事务和不支持事务的两种不同实现方式,这样你就能看到他们的区别。
不支持事务的代码
下面是“ModifyAndUnregister(user *model.User)”的非事务函数, 它在“registration.go”文件中。它是对业务函数“ModifyAndUnregister(ruc.UserDataInterface, user)”的一个简单封装,这个业务函数是被事务和非事务代码共享的。
// The use case of ModifyAndUnregister without transaction
func (ruc *RegistrationUseCase) ModifyAndUnregister(user *model.User) error {
return ModifyAndUnregister(ruc.UserDataInterface, user)
}
下面是共享的业务函数"ModifyAndUnregister(ruc.UserDataInterface, user)"的代码,所有的业务逻辑都在这个函数里。
func ModifyAndUnregister(udi dataservice.UserDataInterface, user *model.User) error {
//loggera.Log.Debug("ModifyAndUnregister")
err := modifyUser(udi, user)
if err != nil {
return errors.Wrap(err, "")
}
err = unregisterUser(udi, user.Name)
if err != nil {
return errors.Wrap(err, "")
}
return nil
}
支持事务的代码
下面是相同的业务函数,但支持事务的代码。它在“registrationTx.go”文件中。你要做全部工作就是在“EnableTx()”中调用业务函数。
// The use case of ModifyAndUnregister with transaction
func (rtuc *RegistrationTxUseCase) ModifyAndUnregisterWithTx(user *model.User) error {
udi := rtuc.UserDataInterface
return udi.EnableTx(func() error {
// wrap the business function inside the TxEnd function
return ModifyAndUnregister(udi, user)
})
}
下面是函数“EnableTx()”的实现代码(它在文件“userDataSql.go”中)。 这个代码是在持久层中。它的实现非常简单,只需调用事务库中的函数“TxEnd()”。
func (uds *UserDataSql) EnableTx(txFunc func() error) error {
return uds.DB.TxEnd(txFunc)
}
以上就是为业务函数添加事务支持所需要做的全部工作,其余代码均在事务库中。
如果你想了解更多关于事务库本身的信息,请阅读“一个非侵入的Go事务管理库——工作原理”,
结论:
我对去年写的事务管理代码进行了升级,使其成为一个非侵入式的轻量级事务管理库。当你使用它时,只需要在应用程序中额外增加两三行代码就能搞定,所有其他代码都放在了事务管理库。它很好地将业务代码与数据库事务代码隔离开来,这样你的业务代码里就只有纯粹的业务逻辑。它是一个库而不是框架,所以不论你使用任何框架都可以使用它。
源代码:
完整的源码: "jfeng45/servicetmpl1"
索引:
1 "清晰架构(Clean Architecture)的Go微服务: 事物管理"
2 "清晰架构(Clean Architecture)的Go微服务: 程序容器(Application Container)"
一个非侵入的Go事务管理库——如何使用的更多相关文章
- 一个非侵入的Go事务管理库——工作原理
在上一篇文章"一个非侵入的Go事务管理库--如何使用"中,我讲述了如何使用事务库.有些读者可能读过"清晰架构(Clean Architecture)的Go微服务: 事物管 ...
- Spring事务管理详解_基本原理_事务管理方式
1. 事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,使用JDBC的事务管理机制,就是利用java.sql.Connection对象完成对事务的提交,那在没有Spring帮我们管理事 ...
- Spring003--Spring事务管理(mooc)
Spring事务管理 一.事务回顾 1.1.什么是事务 事务指的是逻辑上的一组操作,这组操作要么全部成功,要么全部失败. 异常情况发生,需要保证:[1]张三将钱转出,李四收到钱.[2]张三钱未成功转出 ...
- SSM框架——以注解形式实现事务管理
上一篇博文<SSM三大框架整合详细教程>详细说了如何整合Spring.SpringMVC和MyBatis这三大框架.但是没有说到如何配置mybatis的事务管理,在编写业务的过程中,会需要 ...
- Java非侵入式API接口即文档工具apigcc
一个非侵入的api编译.收集.Rest文档生成工具.工具通过分析代码和注释,获取文档信息,生成RestDoc文档 前言 程序员一直以来都有一个烦恼,只想写代码,不想写文档.代码就表达了我的思想和灵魂. ...
- (三)Spring框架之事务管理
一.编程式事务管理 Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,事务管理器接口PlatformT ...
- Spring框架——JDBC与事务管理
JDBC JDBCTemplate简介 XML配置JDBCTemplate 简化JDBC模板查询 事务管理 事务简介 Spring中的事务管理器 Spring中的事务管理器的不同实现 用事务通知声明式 ...
- spring实现一个简单的事务管理
前两天给公司的数据库操作加了事务管理,今天博客就更一下这个吧. 先说明:本文只是简单得实现一下事务,事务的具体内容,比如事务的等级,事务的具体实现原理等等... 菜鸟水平有限,暂时还更不了这个,以后的 ...
- 记前端状态管理库Akita中的一个坑
记状态管理库Akita中的一个坑 Akita是什么 Akita是一种基于RxJS的状态管理模式,它采用Flux中的多个数据存储和Redux中的不可变更新的思想,以及流数据的概念,来创建可观察的数据存储 ...
随机推荐
- Spring基础之AOP
一.AOP能解决什么问题 业务层每个service都要管理事务,在每个service中单独写事务,就会产生很多重复性的代码,而且修改事务时,需要修改源码,不利于维护.为此,把横向重复的代码,纵向抽取形 ...
- Python数据分析:pandas玩转Excel(三)
将对象写入Excel工作表. 要将单个对象写入 Excel .xlsx 文件,只需指定目标文件名即可.要写入多个工作表,必须创建具有目标文件名的ExcelWriter对象,并在文件中指定要写入的工作表 ...
- Java中的集合(十三) 实现Map接口的Hashtable
Java中的集合(十三) 实现Map接口的Hashtable 一.Hashtable简介 和HashMap一样,Hashtable采用“拉链法”实现一个哈希表,它存储的内容是键值对(key-value ...
- win服务器管理软件巧利用——如何让服务器管理事半功倍
那些服务器管理大牛估计看到这个标题会笑了,服务器怎么管理,靠自带的远程桌面肯定是远远不够的,要实现上千台服务器同时登陆,没有一个好程序管理,估计得三餐不食为其颠倒. 那么,有什么好的服务器推荐呢?站长 ...
- MySQL 高级—— Join 、索引 、优化
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.Join 查询 1.SQL执行顺序(一般情况下) 1.1 手写顺序: SELECT DISTINCT ...
- Java实现 LeetCode 706 设计哈希映射(数组+链表)
706. 设计哈希映射 不使用任何内建的哈希表库设计一个哈希映射 具体地说,你的设计应该包含以下的功能 put(key, value):向哈希映射中插入(键,值)的数值对.如果键对应的值已经存在,更新 ...
- Java实现 蓝桥杯VIP 算法训练 整数平均值
题目描述 编写函数,求包含n个元素的整数数组中元素的平均值.要求在函数内部使用指针操纵数组元素,其中n个整数从键盘输入,输出为其平均值. (样例说明:5为输入数据的个数,3 4 0 0 2 是以空格隔 ...
- Java实现 LeetCode 38 外观数列
38. 外观数列 「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述.前五项如下: 1 11 21 1211 111221 1 被读作 "one 1" ...
- Java实现约瑟夫斯问题
1 问题描述 引用自<算法设计与分析基础>第三版: 约瑟夫斯问题,是以弗拉瓦斯.约瑟夫斯(Flavius Josephus)的名字命名的.约瑟夫斯是一个著名的犹太历史学家,参加并记录了公元 ...
- 【Python源码剖析】对象模型概述
Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅. 与其他面向对象编程语言相比, Python 有自己独特的一面. 这让很多开发人员在学习 Python 时,多少有些无所 ...