软件工程中的事务管理
软件工程中不可避免遇到事务管理的问题。有2种主流的事务管理模式,编程式事务,声明式事务
-
编程式事务:通过编程代码在业务逻辑时需要时自行实现,粒度更小。
编程式事务需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法,事务的开始、传递和关闭都显式嵌入到函数的业务逻辑里面 -
声明式事务:通过注解、装饰模式或者XML配置实现。声明式事务是一种AOP式事务管理,可以统一地在函数执行前后进行事务开启和关闭,而不侵入函数的业务逻辑
编程式事务
例子如下:
...
func get_user_by_id(id: int) -> User:
with db.session.begin():
user = db.session.query(User).filter(User.id == id).first()
return user
显式控制事务,事务代码与业务逻辑代码耦合,但是显式也更清晰、控制粒度更细
编程式事务是最自然、最朴素的事务管理模式,任何语言都可以自然地进行编程式事务
声明式事务
java spring 的事务注解是典型的声明式事务(AOP事务)实践
python 声明式事务的实现
python flask中可以借助装饰模式模拟java spring的事务注解
from enum import Enum
from app.flask_extension.plugin import db # db is an instance of class: `flask_sqlalchemy.SQLAlchemy`
class TransactionPropagation(Enum):
"""
事务传播方式
"""
REQUIRED = "REQUIRED" # 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
MANDATORY = "MANDATORY" # 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
def transactional(propagation: TransactionPropagation = TransactionPropagation.REQUIRED):
"""
模仿Java Spring的事务注解功能. 默认propagation为REQUIRED
usage example:
`python
@transactional()
def method_A():
# 在这里执行A方法的操作
pass
@transactional()
def method_B():
# 在这里执行B方法的操作
pass
@transactional()
def method_C():
# 在这里执行C方法的操作
pass
@transactional()
def method_D_1():
# aop 模式:利用transactional()装饰器 管理 method_D 的事务
# method_A method_B method_C 在一个事务内执行
method_A()
method_B()
method_C()
def method_D_2():
# 编程式事务模式 + aop模式:显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法来管理 method_D 的事务
# method_A method_B method_C 在一个事务内执行
with db.session.begin():
method_A()
method_B()
method_C()
`
在 `method_D_1()` 和 `method_D_2()`中,我们按顺序调用这些方法,它们会在一个事务中执行
"""
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:
# 如果当前存在事务,则加入该事务
if db.session().in_transaction():
print(f"执行{f.__name__}前存在已有事务,加入该事务")
result = f(*args, **kwargs)
print(f"{f.__name__}执行完毕")
return result
# 如果当前不存在事务,则创建一个新的事务
try:
db.session.begin()
print(f"{f.__name__}执行前,开启一个新事务")
result = f(*args, **kwargs)
print(f"{f.__name__}执行完毕")
db.session.commit()
print(f"{f.__name__}执行完毕后,事务提交完毕")
return result
except Exception as e:
db.session.rollback()
print(f"{f.__name__}执行出现Exception{e},事务回滚")
raise e
finally:
pass
elif propagation == TransactionPropagation.MANDATORY:
# 如果当前存在事务,则加入该事务
if db.session().in_transaction():
print(f"执行{f.__name__}前存在已有事务,加入该事务")
result = f(*args, **kwargs)
print(f"{f.__name__}执行完毕")
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
这里,通过装饰模式实现声明式事务,解决 scoped_session
对象上的事务开关问题。这是根本问题,不需要每个方法都写事务判断,按需声明propagation即可
golang 声明式事务的实现
因为python是动态语言,所以python的装饰器实现起来很丝滑,不管什么签名的函数都可以统一被装饰
但是golang是强类型语言,一般地,golang中搞装饰模式,只能装饰那些特定签名的函数。在软件工程中,业务逻辑函数的签名有成百上千种,怎么像python那样实现统一的装饰呢?🤔️
- 要装饰不同签名的函数,装饰函数就需要接受不同签名的函数,于是只能
interface{}
承载 - 用
interface{}
承载,那么就需要reflect
获取函数真实的类型
下面给出一个通用Print装饰器的实现
// 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() {
// 函数字面量在 Go 中被视为第一类值(First-Class Value),这意味着你可以将它们分配给变量、作为参数传递给其他函数,或作为其他函数的返回值。然而,你不能直接获取一个函数字面量的地址,因为函数本身并不是一个可寻址的存储位置。
add := Add // 函数字面量Add必须先赋值给一个变量。
// PrintDecorate去装饰Add,得到的新函数赋值给add
PrintDecorate(&add, Add)
res := add(1, 2)
fmt.Printf("----result is %+v-----\n", res)
}
参考:https://juejin.cn/post/7115343063119036453
这样,我们就可以实现Transactional
装饰器,然后Transactional(&decoratedFunc, OriginalFunc)
,如下:
// You can edit this code!
// Click here and start typing.
package main
import (
"fmt"
"reflect"
)
// Transactional 通用的事务装饰器
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 加入声明式事务的Add方法
func TransactionalAdd(a, b int) int {
add := Add
Transactional(&add, Add)
return add(a, b)
}
// TransactionalSubtract 加入声明式事务的Subtract方法
func TransactionalSubtract(a, b int) int {
subtract := Subtract
Transactional(&subtract, Subtract)
return subtract(a, b)
}
func main() {
// 调用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)
}
网友评论