David Chao

David Chao

#
php 筆記 筆記

Laravel Transaction & Events 問題

2020/04/28

大家都知道 DB 的 Transaction 在做多表儲存時是必用的方式,Model 的 Events 也是好東西,大部分的時候都用得很暢快!

由於最近我們把搜尋的部份完全交由 Elastic 處理,所以維持 DB 跟 Elastic 資料一致就很重要,做法就是透過 Event 在 Model Created / Updated 的時候去更新 Elastic。

當我們用的很爽的時候才發現問題,Event 會在 Transaction 完成之前被觸發,想像一下、使用者新增一筆資料,送出儲存然後因為資料有問題被 Rollback,但 Event 會先觸發,所以資料被更新到 Elastic 了,本來以為這是個小問題,實際上解下去才發現,真是麻煩!

不拋棄 Transaction 與 Events 且盡可能不改動原本 Code 的前提下,我們總共嘗試了幾種辦法:

1. 註冊一個 Subscriber 專門去聽 Transaction 相關的 Event,在 Transaction 開始的時候先擋下來,Commit 的時候再去觸發 Event。

好處是完全不用動到原本的 Code,壞處是,如果 Transaction 中有相關聯的動作,就會有問題,例如以下這樣的寫法會拿不到 id,所以改動方面必須把有關連行為的動作移出 Transaction 自行判斷。

2. 用 Queue 的方式解決,原本的 Event 改把更新 Elastic 的部份丟到 Queue 去執行,然後更新 Elastic 的時候加上 refresh 參數,確保我們 update or create 可以立即刷新,這樣如果 Rollback 的 Event 觸發時就不會找不到該筆資料。

這寫法改動幅度不大,把原本的 Event 丟到 Queue,Transaction Rollback 的部份再寫一個還原的邏輯也是丟到 Queue 去就可以了,缺點是要確定 Queue 的 Proccesser 只有一個,不然可能同時執行。

3. 手動關掉 Event,在 Transaction 開始的時候關掉 Event,Commit 後再觸發,這是最保險的方式,可以參考以下作法。

Laravel version 5.7 to 6.x,在 Model 新增 Function

Laravel 5.6 與之前的版本

結論我們是先選擇了 2,但是因為那時候沒想到 3,綜合考量複雜、可讀性、未來出錯機率、好不好維護等等,3 會是最好的方法,就算有新的工程師沒去看 wiki,看到這段 Code 也應該能聯想到一些事情。

所以如果有一樣的需求,建議是用第 3 種方式,但如果又是 5.6 之前的版本的話,不確定會不會因為關閉 Model Event 而影響其他的操作,這就要測試看看了,但還是建議升版吧!

參考資料:

Eloquent events aware of database transactions.

Is it possible to temporarily disable event in Laravel?

發佈留言