一個非侵入的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)”
3 “一個非侵入的Go事務管理庫——工作原理”
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※幫你省時又省力,新北清潔一流服務好口碑
※回頭車貨運收費標準