在維護大型專案時,隨著時間的推移,專案規模往往會不斷擴大,導致邏輯的複雜度逐漸提高,進而增加維運成本。而在微服務架構中,雖然能有效拆分系統功能,卻也因設計上的複雜性、服務間的交互處理以及整合測試的挑戰,使得實施和維護變得困難。因此,需要一種既能平衡大專案的規模與維運成本,又能克服微服務架構設計與測試挑戰的解決方案,以提供穩定且高效的開發模式。
在單體式架構(Monolithic Architecture)的設計方案中,通常會將所有功能模組(如使用者介面、商業邏輯、資料存取等)集中於單一可部署單元內。由於缺乏明確的架構定義,單體式架構在初期的開發與部署上相對簡單且高效。然而,隨著應用程式規模的擴大,內部邏輯的相互依賴與耦合會逐漸加深,最終可能形成所謂的 big ball of mud
(大泥球)。此外,這種架構容易產生 model-code gap(模型與程式碼的不一致),使後續的開發者在閱讀與理解系統時面臨挑戰,進而增加維護成本與風險。
The code structure should reflect the architecture intent
Simon Brown @simonbrown4821
以下整理幾種打包方式: Package By Layer
Package By Feature
Port and Adapter
Package By Component
在 Three-Tier Architecture(三層式架構)中,會明確定義垂直分層與依賴關係,並根據實作內容對功能進行分層設計。當新增一個功能時,每個功能會依據分層進行設計,例如表示層、業務邏輯層和資料存取層,並形成清晰的垂直依賴關係。
相同功能的程式碼會集中放置在一起,使開發者能快速找到與功能相關的所有程式碼,提升開發效率。
當專案中有多個功能時,這些功能會以多條垂直依賴方向進行規劃,並依據分層進行模組化打包。然而,雖然這種設計方法在功能定位與程式碼管理上具有優勢,但也可能帶來一些潛在問題。例如,開發者可能因為過於依賴既有模式,導致貨物崇拜程式設計(Cargo cult programming)的習慣出現。此外,當功能間存在互相依賴時,容易產生程式碼異味(Code Smell),進而影響系統的可維護性與可擴展性。
為了解決開發過程中相互依賴混亂的問題,有部分團隊選擇將功能進行封裝,以避免直接的耦合。然而,僅僅透過功能封裝並不足以解決根本問題,因為這種方式缺乏明確的功能溝通與協作機制。當功能之間需要交互時,若沒有清晰的界定與規範,仍可能導致隱性依賴與結構性問題,進一步影響系統的可維護性與擴展性。
Clean Architecture(也稱為 Hexagonal Architecture 或 Port and Adapter)的核心原則是依賴方向應該從外向內,並以 Business Layer(Domain)作為最重要的核心。外層的元件需要依賴這個核心。Business Layer 負責定義商業邏輯,並透過 Interface(介面)提供依賴方法,讓外圍的元件進行互動。
這種架構的最大特點在於與 Package by Layer 設計相比,將 Data Layer 的依賴方向反向,改為依賴 Business Layer,而非 Business Layer 依賴 Data Layer。不過,雖然這樣的設計更符合清晰的依賴規範,但也可能帶來一些挑戰。
其中一個常見問題是在 Business Layer 中容易出現貨物崇拜程式設計(Cargo cult programming)的習慣,導致過多的 Domain 被單獨建立。這種情況下,每個 Domain 都需要額外建立大量的 Service 和 Interface 來區分 Domain 邊界,從而導致架構變得過於複雜。此外,當系統中存在許多 Domain 時,也無法完全避免 Repository 之間互相依賴的情況,甚至可能回到與 Package by Layer 類似的結果,降低架構的清晰度與維護性。
因此,在採用這種架構時,除了遵循 Clean Architecture 的核心原則外,還需要謹慎規劃 Domain 邊界,避免過度細分與複雜化,並確保設計的實用性與可維護性。
在延續以 Business Layer 為核心的架構設計中,採用模組化的原則,將同一個 Domain 的邏輯集中打包為一個獨立的 Component。這些 Component 負責處理特定領域的業務邏輯,並提供清晰的接口供外部使用。同時,Web Layer 作為外部與業務邏輯之間的橋樑,僅依賴這些 Component,維持其乾淨與簡潔的設計。
在這種架構中,Web Layer 通常對應於一個具體的功能模組,並可能依賴多個 Component 來完成該功能所需的業務邏輯處理。此外,Web Layer 也承擔著整合 Component 間邏輯的角色,確保不同 Component 之間的交互流程清晰且一致。透過這種收束設計,可以使 Web Layer 成為檢視系統邏輯交互的窗口,讓開發者能輕易理解各 Component 之間的運作方式,進一步提升系統的可讀性與維護性。
單體式架構中的 Package By Component 在理念上與微服務架構(Microservice)有一定的相似之處。微服務架構將同一個 Domain 的邏輯集中封裝為獨立的服務,隱藏內部實作,並透過公開的 API 與其他 Domain 進行溝通。同樣地,Package By Component 也是基於相似的設計理念,但它仍然以單體式架構(Monolithic Architecture)為基礎進行規劃。
在 Package By Component 中,邊界的定義並非透過應用程式或容器來劃分,而是透過封裝來控制潛在的依賴。每個 Component 都會公開其 "API"(以 public 方法或介面的形式),用於與其他 Component 進行交互。然而,這些公開的 API 必須符合架構設計的核心意圖,確保邊界清晰,並維持系統的可讀性與可維護性。
這種設計方法在邏輯上與微服務的分層與封裝相似,但由於其基礎仍是單體式架構,因此所有的 Component 最終仍然部署在同一個應用程式內。這使得 Package By Component 能夠在兼顧模組化設計的同時,避免分散式系統的複雜性,成為一種權衡之下的解決方案。
在架構設計中,我們應該以如何幫助開發者提升開發效率與程式碼品質為核心目標。架構的作用在於引導開發者,減少不必要的依賴與程式碼異味,而非僅僅制定一套固定的方法並強制遵守。僵化的規則可能會導致額外的依賴關係與設計負擔,反而有悖於架構的初衷。因此,根據實際情況適時調整規則,並讓規則與具體實作相互融合,才能更靈活地滿足業務需求,同時保持系統的穩定性與可維護性。