閒談軟體設計:拆解子領域

更新 發佈閱讀 9 分鐘
圖片來源:ChatGPT 生成

圖片來源:ChatGPT 生成

通常,一個混亂的系統,不是軟體工程師刻意為之。

系統開發初期,不管是用 DDD、通靈或是其他方式,大多還是會將系統分成幾個模組,讓系統不會變成一團亂。但隨著未來的業務複雜度漸增,原有設計就會開始顯得有點混亂。

就像一開始請室內設計師幫新房子依照「目前」的需求做好規劃,但隨著成員增加,物品累積,原先看起來很漂亮的房子也變成一團亂。這時候,有兩個選擇,與混亂共處,或是整理打掃,這一點在軟體開發也是一樣的。

今天就來分享一下最近剛完成的整理過程。

歷史演進

系統一開始設計時,針對顯而易見的子領域就進行了模組化,這些顯而易見的子領域通常是通用子領域,例如 IAM 和 NCC,用 Maven 的模組獨立於核心子領域之外,除了這些子領域,也把與第三方整合的抽象層都各別獨立成 Maven 模組,並透過模組相依管理,確保相依符合 Clean Architecture 由外向內的方向。

在核心子領域中,根據業務邏輯再拆分成更小的子領域,而這些子領域則是透過 Java 的 package 進行管理,雖然沒有用 Java 9 Module System 進一步限制存取規則,但透過自律,整體都還算是有條不紊。

在後續的開發過程中,不只歷經開發方向調整,也配合市場需求對優先序調整,因此在核心子領域中,有個業務子領域快速膨脹,在該 package 中,檔案數來到 233 個,開始出現在該 package 中尋找檔案越來越不容易。

盤根錯節

於是開始思考:這個業務子領域真的有這麼大嗎?還是有多個業務子領域被放到同一個 package 中了?仔細檢視後發現,問題不在於程式碼變多,而在於邊界消失了。這個 package 裡有三個明顯的業務子領域:組別、班表和調查,只是在當初摸索商業邏輯的過程中,邊界比較模糊而沒有被分而治之,該是時候打掃打掃了。

但在拆子領域時馬上就遇到困難了,在架構上,有限制跨 package 不能直接存取非公開的類別或介面,例如 com.abc.xyz 不能直接存取 com.abc.def 的非公開類別或介面,但在同個 package 中,就沒有這限制,例如在指定班次時,會檢查該成員是否有在該組別中,此時會直接用組別的 repository 先檢查組別是否存在,再用成員的 repository 取出資料後檢查是否在該組別中。

這在同個 package 中很容易就直接使用了,但在拆子領域時,組別的程式搬到另一個 package 中,而 repository 不是公開要給外面使用的介面,如果仍是直接存取,不只是違反限制,也失去拆子領域的意義,因為程式碼仍是盤根錯節,到處耦合。

相依轉移

那怎麼辦?此時,就想起那句經典名言:

All problems in computer science can be solved by another level of indirection.

所以,無法解決問題就多加一層抽象。

回到原始問題:為什麼需要存取 repository?目的其實是為了檢查「成員是否屬於該組別」,重點不是在查詢資料庫,而是在驗證一個業務規則。既然如此,我們可以提供一個抽象 ScopeVerifier,給定組別與成員的 ID,如果成員不在該組別中,就拋出對應的例外:

而原本的程式碼反而變得更加簡潔:

有時候有人會覺得這只是把問題往外拋,並沒有真的解決問題啊,答對了,這不是逃避問題,而是把責任移到更合適的層次。在 Clean Architecture 中,最外層的橋接器 (adapters) 最適合處理這些「髒活」,於是把「透過資料庫」檢查成員是否屬於該組別的責任拋到外層。

而外層在實作時,有兩種方式,一個是寫專屬的 SQL,另一個則是用既有的 repository 組合。看類別圖可能比較有感覺,在未重構前,所有的檔案都在同一個 module (粗線區隔) 的同一個 package (粗虛線框) 中,彼此之間互有關連。

vocus|新世代的創作平台

在重構後,明顯可以看出模組類有兩個 package,而 package 和 package 之間彼此沒有任何關聯。不論是 ScopeVerifierImpl 用哪種方式實作,重點是確保了 Clean Architecture 的內部 (core module) 是整潔的。

vocus|新世代的創作平台

反腐層

在 DDD 的實踐裡,ScopeVerifier 通常會被稱作反腐層,確保上游子領域的變動不會影響到下游子領域,但從 ScopeVerifier 這個介面去體會防腐層的作用比較無感,畢竟 Repository 的介面通常比較穩定,不太會變動。

可能受到要避免重複的程式碼這個深植在很多工程師心中的原則,所以常常會複用 reuse 既有的類別或資料結構,例如,一開始設計時,Survey 會有一些限制,因此有個 Limitation 的資料結構保存這些限制,後來為了讓減少使用者設定的頻率,讓組別有預設的限制,建立問卷時帶入預設的設定,如果不需要變動,可以快速地完成設定。

vocus|新世代的創作平台

當時決定複用既有的資料結構,但後來在 Limitation 中新增屬性,在處理向下相容性時,Squad 和 Survey 希望的預設值不同,變成同個資料結構,卻有兩套反序列 (deserialization) 邏輯。反而在重構後,這問題解決了,還提供了一個更具語意的新資料結構 PresetLimitation

vocus|新世代的創作平台

在重構的過程中,不只是介面或是 Entity 會受到影響,Read Model 也會受到影響。例如,為了讓 app 端一次就取得需要的資料,設計了專屬的 Read Model 將資料先聚合起來,當初也是共用了 SimpleSquad。後來有個功能是問卷才需要的設定,把該設定加到 SimpleSquad 是小事,但卻影響到班表相關的程式碼,可見這個贈與 (額外的欄位) 對班表這個子領域是不受歡迎的。

vocus|新世代的創作平台

在重構後,彼此都有自己專屬的資料結構,看起來好像有重複的程式碼,但回想起當初看《Clean Architecture》,關於重複的程式碼,有一段有趣的描述:

想像一下,兩個使用案例有相似的畫面結構,架構師會試圖共用資料結構,但應該嗎?是真的重複還是偶然?它們只是巧合相似,隨著時間,這兩個畫面會逐漸分歧,最後完全不同。

而這次的例子就正好印證了這件事,不同的 Read Model (View Model) 通常最後都會長的不一樣。

vocus|新世代的創作平台

保護傘

經過重構後,從一個 233 個檔案盤根錯節的 package,變成三個分別有 103 個檔案、74 個檔案和 70 個檔案的 packages,總數 (247) 看起來增加,但卻是三個語意清晰彼此獨立的子領域。

但重構這件事要很小心,整個過程中,有的 PR 其異動到的檔案數超過 600,如果沒有單元測試及整合測試的保護,我大概會選擇繼續走鋼絲,在新增功能時繼續與混亂共處,畢竟找檔案在 IDE 的協助下也不是真的很困難。

沒有測試的重構,其實只是豪賭。這次重構真的是全靠過去累積下來的單元測試保護著。

小結

一個混亂的系統,往往不是刻意為之,而是從需求不明到需求明確的探索過程所留下的結果,但不代表混亂的系統就無法改善,這次的重構,讓原先錯綜複雜的子領域,切割成三個語意完整且彼此獨立的子領域,讓系統再次恢復秩序。

與混亂共處,或是整理打掃,往往只在一念之間。問題從來不是系統有多混亂,而是有沒有想讓它變好的行動。


題外話:雖然說我常常提到 Clean Architecture,但我並沒有 100% 遵循書中描述的規則,例如,我會把 Entity 傳遞出去,違反書中的建議,但我個人仍傾向保持一個好的平衡性即可,對我來說過著潔癖的生活其實是很辛苦的。

留言
avatar-img
Spirit 異想世界
58會員
122內容數
這是從 Medium 開始的一個專題,主要是想用輕鬆閒談的方式,分享這幾年軟體開發的心得,原本比較侷限於軟體架構,但這幾年的文章不僅限於架構,也聊不少流程相關的心得,所以趁換平台,順勢換成閒談軟體設計。
Spirit 異想世界的其他內容
2026/03/15
本文探討如何在 Javalin 環境下,透過 Interface-based Typed Configuration 實現集中管理、型別安全、慣例優先、容易實作與隔離來源的配置管理方案,並與 Spring framework 的配置方式進行比較,強調此方案的輕量、實用與可擴展性。
Thumbnail
2026/03/15
本文探討如何在 Javalin 環境下,透過 Interface-based Typed Configuration 實現集中管理、型別安全、慣例優先、容易實作與隔離來源的配置管理方案,並與 Spring framework 的配置方式進行比較,強調此方案的輕量、實用與可擴展性。
Thumbnail
2026/03/08
開發系統過程中常遇到 race condition 問題,文章探討了樂觀鎖、Serializable Isolation Level 交易的限制,並提出 PostgreSQL Advisory Lock 作為一種方便且無需額外部署基礎設施的分散式鎖解決方案,以確保批次寫入與背景運算能按順序執行。
Thumbnail
2026/03/08
開發系統過程中常遇到 race condition 問題,文章探討了樂觀鎖、Serializable Isolation Level 交易的限制,並提出 PostgreSQL Advisory Lock 作為一種方便且無需額外部署基礎設施的分散式鎖解決方案,以確保批次寫入與背景運算能按順序執行。
Thumbnail
2026/03/01
本文探討在軟體開發過程中,將原本並非 Aggregate 的設計,逐步重構成具有 Aggregate 精神的實作。作者分享了一個實際案例,說明如何在一致性問題和更複雜的協同作業需求出現時,透過引入樂觀鎖,並逐步思考 Aggregate 的設計原則,最終找到一個在滿足系統需求下的特殊解決方案。
Thumbnail
2026/03/01
本文探討在軟體開發過程中,將原本並非 Aggregate 的設計,逐步重構成具有 Aggregate 精神的實作。作者分享了一個實際案例,說明如何在一致性問題和更複雜的協同作業需求出現時,透過引入樂觀鎖,並逐步思考 Aggregate 的設計原則,最終找到一個在滿足系統需求下的特殊解決方案。
Thumbnail
看更多
你可能也想看
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
本篇文章簡要介紹自動化測試的基本概念及相關的測試套件,並探討了在CI/CD流程中整合自動化測試的重要性,及如何使用Faker和Mockery來生成測試資料和模擬物件。最後,分享了自動化測試在後端開發中的必要性與實踐建議,旨在提升程式碼品質及降低維護成本。
Thumbnail
本篇文章簡要介紹自動化測試的基本概念及相關的測試套件,並探討了在CI/CD流程中整合自動化測試的重要性,及如何使用Faker和Mockery來生成測試資料和模擬物件。最後,分享了自動化測試在後端開發中的必要性與實踐建議,旨在提升程式碼品質及降低維護成本。
Thumbnail
在這篇文章中,我們將介紹工作與以前念書時期在開發流程上的差異,並深入瞭解CI/CD、Travis CI以及加解密的應用。 CI/CD是自動化的軟體開發實踐,而加解密則是保護機密資料安全的重要技術。
Thumbnail
在這篇文章中,我們將介紹工作與以前念書時期在開發流程上的差異,並深入瞭解CI/CD、Travis CI以及加解密的應用。 CI/CD是自動化的軟體開發實踐,而加解密則是保護機密資料安全的重要技術。
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
背景:從冷門配角到市場主線,算力與電力被重新定價   小P從2008進入股市,每一個時期的投資亮點都不同,記得2009蘋果手機剛上市,當時蘋果只要在媒體上提到哪一間供應鏈,隔天股價就有驚人的表現,當時光學鏡頭非常熱門,因為手機第一次搭上鏡頭可以拍照,也造就傳統相機廠的殞落,如今手機已經全面普及,題
Thumbnail
團隊最近因為有大型功能要發佈,因此剛完成了一次捕蟲大會(Bug Bash),趁著記憶猶新,來寫一下在舉辦過程中可以注意的一些重點。除了自己紀錄,也希望對看到文章的你有點幫助。
Thumbnail
團隊最近因為有大型功能要發佈,因此剛完成了一次捕蟲大會(Bug Bash),趁著記憶猶新,來寫一下在舉辦過程中可以注意的一些重點。除了自己紀錄,也希望對看到文章的你有點幫助。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
使用 usbmuxd 和 iperf3 實測 iPhone 16 Pro Max 的 USB 3 可達 6 Gb/s,比 USB 2.0 快 20 倍。並且能從數據算出虛擬網路層的損耗。
Thumbnail
使用 usbmuxd 和 iperf3 實測 iPhone 16 Pro Max 的 USB 3 可達 6 Gb/s,比 USB 2.0 快 20 倍。並且能從數據算出虛擬網路層的損耗。
Thumbnail
氛圍開發(Vibe Coding)讓寫程式不再像解謎,而像許願! 未來的軟體建構,不再是解一道道的技術難題,更像是對宇宙許下一個願望,然後軟體就誕生了。不是在寫程式,而是在指揮一場 AI 的創造力交響曲。氛圍開發(Vibe coding)將人類抽象想法轉化為清晰指令,與 AI 協作的心法與技法。
Thumbnail
氛圍開發(Vibe Coding)讓寫程式不再像解謎,而像許願! 未來的軟體建構,不再是解一道道的技術難題,更像是對宇宙許下一個願望,然後軟體就誕生了。不是在寫程式,而是在指揮一場 AI 的創造力交響曲。氛圍開發(Vibe coding)將人類抽象想法轉化為清晰指令,與 AI 協作的心法與技法。
Thumbnail
實測 iOS 開發常見情境:拷貝 DeviceSupport、啟動 App、Breakpoint、View Debugger、Memory Graph。結果顯示 USB 開發比無線穩定快速,而 USB 3 在拷貝 DeviceSupport 上的效益最明顯。
Thumbnail
實測 iOS 開發常見情境:拷貝 DeviceSupport、啟動 App、Breakpoint、View Debugger、Memory Graph。結果顯示 USB 開發比無線穩定快速,而 USB 3 在拷貝 DeviceSupport 上的效益最明顯。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文探討自動化測試的核心理念與實際應用,涵蓋如何模擬運行環境、確保程式碼在各種情境下的穩定性,以及進行錯誤處理的方法。文中指出自動化測試的各種優點,並提到設計測試的注意事項。透過使用相關工具和方法,讀者可以有效進行功能測試,並掌握相關技巧以應對常見問題,讓開發過程更為順利。
Thumbnail
本文探討自動化測試的核心理念與實際應用,涵蓋如何模擬運行環境、確保程式碼在各種情境下的穩定性,以及進行錯誤處理的方法。文中指出自動化測試的各種優點,並提到設計測試的注意事項。透過使用相關工具和方法,讀者可以有效進行功能測試,並掌握相關技巧以應對常見問題,讓開發過程更為順利。
Thumbnail
透過 Swift Dependencies 最基本的功能,就做到讓原本無法掌握的系統時間,變得完全可控。讀完肯定讓你躍躍欲試!
Thumbnail
透過 Swift Dependencies 最基本的功能,就做到讓原本無法掌握的系統時間,變得完全可控。讀完肯定讓你躍躍欲試!
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News