vocus logo

方格子 vocus

閒談軟體設計:交易管理

更新 發佈閱讀 11 分鐘

在決定離開 Spring Boot 後,要怎麼處理資料庫的交易管理?連線池可以直接使用 HikariCP,SQL builder 可以使用 Sql2o (雖然 Sql2o 有提供 ORM 相關的功能,但我幾乎沒用到),可是交易管理怎麼辦呢?

Sql2o 有提供 Transaction 相關的封裝,但要怎麼跟 repository pattern 搭配?一直帶著代表 transaction 的物件跑,總覺得怪怪的?整個 repository 的介面一點也不 collection like。此外,還有一個問題,誰呼叫 beginTransactin()commit() 和 rollback() 呢?

交易範圍

要回答誰呼叫 beginTransaction(),其實就是回答誰管理交易範圍,比較偏 DDD 的答案是 application model 或是 use case 負責,一個 use case 就是一個完整的交易範圍,一個 use case 搭配一個 aggregate,透過 repository,整個 aggregate 物件儲存成功,或是全部失敗,不會有半個 aggregate 成功的情況,還可以搭配樂觀鎖 (參見閒談軟體設計:樂觀鎖),確保狀態的正確性。

不過「交易」這件事對我來說一直都是「技術細節」,個人不太喜歡在偏核心的 use case 中管理交易,反而是在 API controller 加上 @Transactional,確保每次 API 的呼叫,全部成功或全部失敗。也因此,這次的設計也是將交易管理做成一個 wrapper,可以搭配先前在閒談軟體設計:安全聲明設計的 secured 一起使用。但本文中討論的設計思維也可以用在別的地方,不是只能寫成 wrapper。

Service Locator

在確定由 API controller 管理交易範圍後,再來就是處理怎麼讓 repository 取得代表 transaction 的物件?一般來說,dependency injection 有幾種方式

  • constructor injection — 建立物件當下就注入需要的相依,但 repository 物件生命週期和 transaction 物件生命週期對不起來,難以用這個方式。
  • method injection — 呼叫對應函式的當下提供需要的相依,上面的範例程式碼的 put(entity, session) 便是這種方式,這方式破壞了 repository 原始的介面。
  • setter injection — 透過 setter 將需要的相依注入,這種方式代表使用 repository 的物件要有 transaction 物件,不然也無法呼叫 setter 注入。
  • interface injection — 實作特定 interface 讓框架知道需要注入特定的相依,會讓設計變複雜。

因為種種因素,上面幾種都不是這次要用的,我們需要一個可以先注入到 repository 中,等到 repository 需要時能提供 transaction 的物件,這時,service locator 就是蠻合適的方式。

從範例可以看到 SessionSource<Connection> 就是 service locator,在第 5 行的 constructor 就注入到 repository 中,而實際的 transaction 物件則是第 12 行才由 service locator 提供 (connection 參數),這讓 repository 維持原有的介面,同時在需要的時候可以取得 transaction 物件。

Thread Local

我們解決了 repository 怎麼取得 transaction 物件,但 SessionSource 長什麼樣子?如果有一個交易範圍需要用到兩個 repository 怎麼處理?不同的 API 請求怎麼隔離彼此的交易範圍?於是又再次請出在閒談軟體設計:日誌框架使用的 ThreadLocal。注意,這設計使用 ThreadLocal 有幾個前提要素:

  • 每個請求都是由獨立的 thread 處理,
  • 每個請求不會跨多個 thread (讓複雜度下降,不然每次切換 thread,必須要有對應的程式碼處理 transaction)

閒談軟體設計:日誌框架中有提到,每個請求都會是一個新的 virtual thread 處理,處理完就銷毀,也沒有使用任何非同步式的框架,因此,這兩點在這次的設計中不是問題。

接著看 SessionSource 的介面,基本上只定義了兩個函式,一個處理無回傳值的,一個處理有回傳值的,當 repository 需要讀資料時用有回傳值的函式,當寫入或更新資料時用無回傳值的函式。

接著提供一個 TransactionManager 介面,主要就是提供開始、提交和回滾的操作。

然後便是實作 TransactionManager,內容其實蠻單純的,當要開啟一個 transaction 時,先檢查 thread local 有沒有已經有一個 transaction 了,如果有就略過,若沒有就建立一個並塞到 thread local 中。

呼叫提交時,檢查是否有 transaction,沒有則拋出例外,有就呼叫底層的提交,並將 transaction 從 thread local 移除;回滾也是類似的行為。

處理完,開啟、提交和回滾後,重點就是 runInSession 的實作了,如果沒有開啟 transaction,例如一個單純只讀取資料的 API,沒有其他特殊的原因,就可能不需要用 transational 包起來,此時就用獨立的 connection 來執行 (runInIsolatedTransactionalConnection)。

如果已經有一個開啟的 transaction,就會用既有的 transaction 來執行。因此,不管有幾個 repository,只要在同個 thread,使用相同的 session source,當已經有開啟的 transaction,都會用到同一個 transaction。

函式風格的聲明

底層的東西都完成差不多了,最後,提供一個 transactional 的函式,將 request handler 包起來,邏輯也很單純,就是在處理 request 前 (第 10 行),先呼叫 begin() (第 9 行),如果沒有錯誤,就呼叫 commit() (第 11 行),若出錯就呼叫 rollback() (第 14 行)。

看到這,可能還是覺得怪怪的,到底怎麼串起來的?整個流程會是這樣:

  • client 呼叫 API
  • Javalin 將請求導向 request handler
  • 由於 request handler 已經被 transactional 包起來,所以 wrapper 先呼叫 begin
  • 接著呼叫真正的 request handler
  • request handler 整理 request body 等參數後,呼叫 service (use case)
  • service 呼叫 repository 儲存這次處理的物件
  • repository 透過 session source 試圖取得 transaction,此時,會得到前面 begin 建立的 transaction
  • 若一切正常,service 回傳資料
  • request handler 將資料轉換成 response
  • wrapper 呼叫 commit
  • response 回到 client

這大概是 OOP 為什麼需要一些前提知識,才能讀懂程式的原因,若沒有這些前提知識,透過中間層解耦的程式區塊,很難理解之間的關聯,但寫程式就是這樣,在各種不同的條件下取捨,本文的設計,是透過 service locator維持 repository collection-like 的介面,並用 thread local 將 transaction 的控制權交給 controller/use case 的一種做法。

小節

終於把閒談軟體設計:Web 框架的選擇中留下的坑補完了,雖然 Spring framework 變得越來越龐大,但確實提供了不少有用的東西,節省開發者不少時間,但 Spring framework 也並不是不得不的選擇,只是有些東西得開發者自己處理,例如本文提到的交易管理。

交易管理可能很複雜 (跨不同種的資料庫),老實說,能用現成被驗證過的技術就盡量用,這次因為選擇不使用 Spring framework,才需要自己手動寫一個,目前用起來沒太大問題,但之後若更複雜了,也許就要考慮成熟的第三方技術,但應該只需要改寫 transaction manager 就是了 (希望)。

留言
avatar-img
Spirit 異想世界
58會員
122內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
Spirit 異想世界的其他內容
2025/12/28
透過實際應用場景,闡述如何設計 Read Model 以優化效能,並討論其與領域模型的關係,同時提出設計時需注意的細節,最後留下關於 Read Model 在架構中的位置的討論。
Thumbnail
2025/12/28
透過實際應用場景,闡述如何設計 Read Model 以優化效能,並討論其與領域模型的關係,同時提出設計時需注意的細節,最後留下關於 Read Model 在架構中的位置的討論。
Thumbnail
2025/12/21
分享瞭如何在新系統中應用樂觀鎖,透過 version 欄位簡化併發控制,同時保持高吞吐量。文章也觸及了樂觀鎖的進階應用及注意事項,並總結了兩種鎖機制的適用場景,為開發者提供實用的選擇指南。
Thumbnail
2025/12/21
分享瞭如何在新系統中應用樂觀鎖,透過 version 欄位簡化併發控制,同時保持高吞吐量。文章也觸及了樂觀鎖的進階應用及注意事項,並總結了兩種鎖機制的適用場景,為開發者提供實用的選擇指南。
Thumbnail
2025/12/14
現今的系統不論是 B to B、B to C 或是 B to B to C,通知都是不可少,不管是簡訊發送 OTP,還是發送臨時密碼的 email,或各式各樣的 push 通知,通知已不可少的環節,這也是為什麼在一開始系統架構設計時,早早把 ncc 規劃成一個獨立模組 (子系統)。
Thumbnail
2025/12/14
現今的系統不論是 B to B、B to C 或是 B to B to C,通知都是不可少,不管是簡訊發送 OTP,還是發送臨時密碼的 email,或各式各樣的 push 通知,通知已不可少的環節,這也是為什麼在一開始系統架構設計時,早早把 ncc 規劃成一個獨立模組 (子系統)。
Thumbnail
看更多
你可能也想看
Thumbnail
創作不只是個人戰,在 vocus ,也可以是一場集體冒險、組隊升級。最具代表性的創作者社群「vocus 野格團」,現在有了更強大的新夥伴加入!除了大家熟悉的「官方主題沙龍」,這次我們徵召了 8 位領域各異的「個人主題專家」,將再度嘗試創作的各種可能,和格友們激發出更多未知的火花。
Thumbnail
創作不只是個人戰,在 vocus ,也可以是一場集體冒險、組隊升級。最具代表性的創作者社群「vocus 野格團」,現在有了更強大的新夥伴加入!除了大家熟悉的「官方主題沙龍」,這次我們徵召了 8 位領域各異的「個人主題專家」,將再度嘗試創作的各種可能,和格友們激發出更多未知的火花。
Thumbnail
看完上篇 4 位新成員的靈魂拷問,是不是意猶未盡?別急,野格團新血的驚喜正接著登場!今天下篇接力的另外 4 位「個人主題專家」,戰力同樣驚人──領域從旅行美食、運動、商業投資到自我成長;這些人如何維持長跑般的創作動力?在爆紅的文章背後,又藏著哪些不為人知的洞察?5 大靈魂拷問繼續出擊
Thumbnail
看完上篇 4 位新成員的靈魂拷問,是不是意猶未盡?別急,野格團新血的驚喜正接著登場!今天下篇接力的另外 4 位「個人主題專家」,戰力同樣驚人──領域從旅行美食、運動、商業投資到自我成長;這些人如何維持長跑般的創作動力?在爆紅的文章背後,又藏著哪些不為人知的洞察?5 大靈魂拷問繼續出擊
Thumbnail
在紡織業做廠長二十多年,習慣了各種挑戰:產線延誤、設備故障、排程變動、來料不齊……但有一個新挑戰,這兩年變得愈來愈明顯,卻也最難處理:碳排放管理與能源效率透明化。這不是什麼新名詞,大家都聽過。問題是:知道歸知道,要做怎麼做?
Thumbnail
在紡織業做廠長二十多年,習慣了各種挑戰:產線延誤、設備故障、排程變動、來料不齊……但有一個新挑戰,這兩年變得愈來愈明顯,卻也最難處理:碳排放管理與能源效率透明化。這不是什麼新名詞,大家都聽過。問題是:知道歸知道,要做怎麼做?
Thumbnail
在這個數位時代,幾乎人人有手機有電腦,每天都會接觸網站或 APP。你打開手機滑臉書、用foodpanda 網路訂餐、上網查資料,甚至於近兩年快速登上科技時代的最佳主角:AI ,背後都是由一群默默耕耘的軟體工程師在支撐。 今天,就讓我們從基礎的網站架構談起,一步步認識工程師的世界。
Thumbnail
在這個數位時代,幾乎人人有手機有電腦,每天都會接觸網站或 APP。你打開手機滑臉書、用foodpanda 網路訂餐、上網查資料,甚至於近兩年快速登上科技時代的最佳主角:AI ,背後都是由一群默默耕耘的軟體工程師在支撐。 今天,就讓我們從基礎的網站架構談起,一步步認識工程師的世界。
Thumbnail
將兩個JPG文件合併為一個是一項實用的技能,可用於創建拼貼、文件或演示文稿。本文探討了多種有效的方法來達成這一目標,包括使用在線工具、桌面應用程序以及操作系統內建的工具。每種方法都有其優缺點,適用於不同的需求和資源。無論您是尋求快速解決方案,還是需要高級編輯功能,這份指南將幫助您找到適合的選項。
Thumbnail
將兩個JPG文件合併為一個是一項實用的技能,可用於創建拼貼、文件或演示文稿。本文探討了多種有效的方法來達成這一目標,包括使用在線工具、桌面應用程序以及操作系統內建的工具。每種方法都有其優缺點,適用於不同的需求和資源。無論您是尋求快速解決方案,還是需要高級編輯功能,這份指南將幫助您找到適合的選項。
Thumbnail
EP27 精華重點: 1.時間管理的觀念: 將時間視為資產,讓時間發揮最大價值 設定明確的短期和長期目標 2.制定可行計畫: 將大目標分割成可執行的小計畫和里程碑 給自己合理的獎勵作為動力 3.優先排序處理事務: 根據重要性和緊急程度劃分為四個象限 先處理緊急的事物,延後的部分要補
Thumbnail
EP27 精華重點: 1.時間管理的觀念: 將時間視為資產,讓時間發揮最大價值 設定明確的短期和長期目標 2.制定可行計畫: 將大目標分割成可執行的小計畫和里程碑 給自己合理的獎勵作為動力 3.優先排序處理事務: 根據重要性和緊急程度劃分為四個象限 先處理緊急的事物,延後的部分要補
Thumbnail
不論你是設計或科技產業的夥伴,可能都會遇到面試官希望你拿出「作品集」,以確認你的經驗與新公司是否適合的狀況。但如果你的作品是在前東家做的,擅自展示可能有哪些侵權風險呢?這篇文章針對面試展示作品集,提供了《營業祕密法》與《著作權法》觀點的注意事項,並建議面試者在展示作品集前應取得前公司的同意授權。
Thumbnail
不論你是設計或科技產業的夥伴,可能都會遇到面試官希望你拿出「作品集」,以確認你的經驗與新公司是否適合的狀況。但如果你的作品是在前東家做的,擅自展示可能有哪些侵權風險呢?這篇文章針對面試展示作品集,提供了《營業祕密法》與《著作權法》觀點的注意事項,並建議面試者在展示作品集前應取得前公司的同意授權。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News