軟件工程中的事務(wù)管理
軟件工程中不可避免遇到事務(wù)管理的問(wèn)題。有2種主流的事務(wù)管理模式,編程式事務(wù)踊谋,聲明式事務(wù)
編程式事務(wù):通過(guò)編程代碼在業(yè)務(wù)邏輯時(shí)需要時(shí)自行實(shí)現(xiàn),粒度更小旋讹。
編程式事務(wù)需要在代碼中顯式調(diào)用beginTransaction()殖蚕、commit()、rollback()等事務(wù)管理相關(guān)的方法沉迹,事務(wù)的開始睦疫、傳遞和關(guān)閉都顯式嵌入到函數(shù)的業(yè)務(wù)邏輯里面聲明式事務(wù):通過(guò)注解、裝飾模式或者XML配置實(shí)現(xiàn)鞭呕。聲明式事務(wù)是一種AOP式事務(wù)管理蛤育,可以統(tǒng)一地在函數(shù)執(zhí)行前后進(jìn)行事務(wù)開啟和關(guān)閉,而不侵入函數(shù)的業(yè)務(wù)邏輯
編程式事務(wù)
例子如下:
...
func get_user_by_id(id: int) -> User:
with db.session.begin():
user = db.session.query(User).filter(User.id == id).first()
return user
顯式控制事務(wù)葫松,事務(wù)代碼與業(yè)務(wù)邏輯代碼耦合瓦糕,但是顯式也更清晰、控制粒度更細(xì)
編程式事務(wù)是最自然腋么、最樸素的事務(wù)管理模式咕娄,任何語(yǔ)言都可以自然地進(jìn)行編程式事務(wù)
聲明式事務(wù)
java spring 的事務(wù)注解是典型的聲明式事務(wù)(AOP事務(wù))實(shí)踐
python 聲明式事務(wù)的實(shí)現(xiàn)
python flask中可以借助裝飾模式模擬java spring的事務(wù)注解
from enum import Enum
from app.flask_extension.plugin import db # db is an instance of class: `flask_sqlalchemy.SQLAlchemy`
class TransactionPropagation(Enum):
"""
事務(wù)傳播方式
"""
REQUIRED = "REQUIRED" # 如果當(dāng)前存在事務(wù),則加入該事務(wù)珊擂,如果當(dāng)前不存在事務(wù)圣勒,則創(chuàng)建一個(gè)新的事務(wù)徐块。
MANDATORY = "MANDATORY" # 如果當(dāng)前存在事務(wù),則加入該事務(wù)灾而;如果當(dāng)前不存在事務(wù)胡控,則拋出異常。
def transactional(propagation: TransactionPropagation = TransactionPropagation.REQUIRED):
"""
模仿Java Spring的事務(wù)注解功能. 默認(rèn)propagation為REQUIRED
usage example:
`python
@transactional()
def method_A():
# 在這里執(zhí)行A方法的操作
pass
@transactional()
def method_B():
# 在這里執(zhí)行B方法的操作
pass
@transactional()
def method_C():
# 在這里執(zhí)行C方法的操作
pass
@transactional()
def method_D_1():
# aop 模式:利用transactional()裝飾器 管理 method_D 的事務(wù)
# method_A method_B method_C 在一個(gè)事務(wù)內(nèi)執(zhí)行
method_A()
method_B()
method_C()
def method_D_2():
# 編程式事務(wù)模式 + aop模式:顯式調(diào)用beginTransaction()旁趟、commit()昼激、rollback()等事務(wù)管理相關(guān)的方法來(lái)管理 method_D 的事務(wù)
# method_A method_B method_C 在一個(gè)事務(wù)內(nèi)執(zhí)行
with db.session.begin():
method_A()
method_B()
method_C()
`
在 `method_D_1()` 和 `method_D_2()`中,我們按順序調(diào)用這些方法锡搜,它們會(huì)在一個(gè)事務(wù)中執(zhí)行
"""
def _transactional(f):
@wraps(f)
def decorated_function(*args, **kwargs):
supported_propagation = [cur_propagation for cur_propagation in TransactionPropagation]
if propagation not in supported_propagation:
raise ValueError(f"{propagation} not in supported_propagation: {supported_propagation}")
print(f"{f.__name__}使用的propagation是{propagation}")
if propagation == TransactionPropagation.REQUIRED:
# 如果當(dāng)前存在事務(wù)橙困,則加入該事務(wù)
if db.session().in_transaction():
print(f"執(zhí)行{f.__name__}前存在已有事務(wù),加入該事務(wù)")
result = f(*args, **kwargs)
print(f"{f.__name__}執(zhí)行完畢")
return result
# 如果當(dāng)前不存在事務(wù)耕餐,則創(chuàng)建一個(gè)新的事務(wù)
try:
db.session.begin()
print(f"{f.__name__}執(zhí)行前凡傅,開啟一個(gè)新事務(wù)")
result = f(*args, **kwargs)
print(f"{f.__name__}執(zhí)行完畢")
db.session.commit()
print(f"{f.__name__}執(zhí)行完畢后,事務(wù)提交完畢")
return result
except Exception as e:
db.session.rollback()
print(f"{f.__name__}執(zhí)行出現(xiàn)Exception{e}肠缔,事務(wù)回滾")
raise e
finally:
pass
elif propagation == TransactionPropagation.MANDATORY:
# 如果當(dāng)前存在事務(wù)夏跷,則加入該事務(wù)
if db.session().in_transaction():
print(f"執(zhí)行{f.__name__}前存在已有事務(wù),加入該事務(wù)")
result = f(*args, **kwargs)
print(f"{f.__name__}執(zhí)行完畢")
return result
else:
msg = f"{f.__name__}'s propagation type is {propagation}, but no transaction provided"
print(msg)
raise ValueError(msg)
return decorated_function
return _transactional
這里明未,通過(guò)裝飾模式實(shí)現(xiàn)聲明式事務(wù)槽华,解決 scoped_session
對(duì)象上的事務(wù)開關(guān)問(wèn)題。這是根本問(wèn)題趟妥,不需要每個(gè)方法都寫事務(wù)判斷猫态,按需聲明propagation即可
golang 聲明式事務(wù)的實(shí)現(xiàn)
因?yàn)閜ython是動(dòng)態(tài)語(yǔ)言,所以python的裝飾器實(shí)現(xiàn)起來(lái)很絲滑披摄,不管什么簽名的函數(shù)都可以統(tǒng)一被裝飾
但是golang是強(qiáng)類型語(yǔ)言亲雪,一般地,golang中搞裝飾模式疚膊,只能裝飾那些特定簽名的函數(shù)义辕。在軟件工程中,業(yè)務(wù)邏輯函數(shù)的簽名有成百上千種酿联,怎么像python那樣實(shí)現(xiàn)統(tǒng)一的裝飾呢终息????
- 要裝飾不同簽名的函數(shù),裝飾函數(shù)就需要接受不同簽名的函數(shù)贞让,于是只能
interface{}
承載 - 用
interface{}
承載,那么就需要reflect
獲取函數(shù)真實(shí)的類型
下面給出一個(gè)通用Print裝飾器的實(shí)現(xiàn)
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
"reflect"
)
// PrintDecorate 通用的打印裝飾器
func PrintDecorate(decoratedFuncPtr, f interface{}) {
ori_func := reflect.ValueOf(f)
wrapFunc := func(in []reflect.Value) []reflect.Value {
// before do something...
fmt.Println("before do something...")
ret := ori_func.Call(in)
// after do something...
fmt.Println("after do something...")
return ret
}
decoratedFuncValue := reflect.MakeFunc(ori_func.Type(), wrapFunc)
decoratedFunc := reflect.ValueOf(decoratedFuncPtr).Elem()
decoratedFunc.Set(decoratedFuncValue)
return
}
func Add(a, b int) int {
fmt.Println("do adding")
return a + b
}
func main() {
// 函數(shù)字面量在 Go 中被視為第一類值(First-Class Value)柳譬,這意味著你可以將它們分配給變量喳张、作為參數(shù)傳遞給其他函數(shù),或作為其他函數(shù)的返回值美澳。然而销部,你不能直接獲取一個(gè)函數(shù)字面量的地址摸航,因?yàn)楹瘮?shù)本身并不是一個(gè)可尋址的存儲(chǔ)位置。
add := Add // 函數(shù)字面量Add必須先賦值給一個(gè)變量舅桩。
// PrintDecorate去裝飾Add酱虎,得到的新函數(shù)賦值給add
PrintDecorate(&add, Add)
res := add(1, 2)
fmt.Printf("----result is %+v-----\n", res)
}
參考:https://juejin.cn/post/7115343063119036453
這樣,我們就可以實(shí)現(xiàn)Transactional
裝飾器擂涛,然后Transactional(&decoratedFunc, OriginalFunc)
读串,如下:
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
"reflect"
)
// Transactional 通用的事務(wù)裝飾器
func Transactional(decoratedFuncPtr, f interface{}) {
ori_func := reflect.ValueOf(f)
wrapFunc := func(in []reflect.Value) []reflect.Value {
// before do something...
fmt.Println("open new transaction, or reentrant existed transaction")
ret := ori_func.Call(in)
// after do something...
fmt.Println("commit/rollback transaction, or do nothing")
return ret
}
decoratedFuncValue := reflect.MakeFunc(ori_func.Type(), wrapFunc)
decoratedFunc := reflect.ValueOf(decoratedFuncPtr).Elem()
decoratedFunc.Set(decoratedFuncValue)
return
}
func Add(a, b int) int {
fmt.Println("do adding")
return a + b
}
func Subtract(a, b int) int {
fmt.Println("do subtracting")
return a - b
}
// TransactionalAdd 加入聲明式事務(wù)的Add方法
func TransactionalAdd(a, b int) int {
add := Add
Transactional(&add, Add)
return add(a, b)
}
// TransactionalSubtract 加入聲明式事務(wù)的Subtract方法
func TransactionalSubtract(a, b int) int {
subtract := Subtract
Transactional(&subtract, Subtract)
return subtract(a, b)
}
func main() {
// 調(diào)用TransactionalAdd和TransactionalSubtract即可
res_add := TransactionalAdd(1, 2)
fmt.Printf("result is %+v\n", res_add)
res_sub := TransactionalSubtract(1, 2)
fmt.Printf("result is %+v\n", res_sub)
}