軟件工程中的事務(wù)管理(未允禁轉(zhuǎn)) 2023-10-24

軟件工程中的事務(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)
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者撒妈。
  • 序言:七十年代末恢暖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子狰右,更是在濱河造成了極大的恐慌杰捂,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棋蚌,死亡現(xiàn)場(chǎng)離奇詭異嫁佳,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)谷暮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門脱拼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)坷备,“玉大人熄浓,你說(shuō)我怎么就攤上這事省撑。” “怎么了竟秫?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肥败。 經(jīng)常有香客問(wèn)我趾浅,道長(zhǎng)馒稍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任纽谒,我火速辦了婚禮,結(jié)果婚禮上鼓黔,老公的妹妹穿的比我還像新娘央勒。我一直安慰自己,他們只是感情好崔步,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著井濒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪眼虱。 梳的紋絲不亂的頭發(fā)上喻奥,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天捏悬,我揣著相機(jī)與錄音,去河邊找鬼过牙。 笑死甥厦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寇钉。 我是一名探鬼主播刀疙,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扫倡!你這毒婦竟也來(lái)了谦秧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤撵溃,失蹤者是張志新(化名)和其女友劉穎疚鲤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缘挑,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡集歇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了语淘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诲宇。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惶翻,靈堂內(nèi)的尸體忽然破棺而出姑蓝,到底是詐尸還是另有隱情,我是刑警寧澤维贺,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布它掂,位于F島的核電站,受9級(jí)特大地震影響溯泣,放射性物質(zhì)發(fā)生泄漏虐秋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一垃沦、第九天 我趴在偏房一處隱蔽的房頂上張望客给。 院中可真熱鬧,春花似錦肢簿、人聲如沸靶剑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)桩引。三九已至,卻和暖如春收夸,著一層夾襖步出監(jiān)牢的瞬間坑匠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工卧惜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厘灼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓咽瓷,卻偏偏與公主長(zhǎng)得像设凹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茅姜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355