架構分解#
架構模組化解釋了為什麼(why)要拆分單體應用,架構分解則描述了如何(how)拆分。拆解大型複雜的單體應用是一項耗時且困難的工作,因此首先必須判斷是否可行以及該採用何種方法。
避免 Elephant Migration Anti-Pattern——一次拆一塊(one bite at a time)看似合理,但往往導致一個沒有結構的分散式泥球(distributed monolith)。應該採取整體性的方法論。
Is the Codebase Decomposable?#

Figure 4.1: The decision tree for selecting a decomposition approach
在開始分解之前,架構師必須先評估程式碼庫是否具備足夠的內部結構來進行分解。若程式碼庫是一個 Big Ball of Mud(泥球架構),缺乏內部結構,則需要不同的處理策略。
Afferent and Efferent Coupling#
1979 年 Edward Yourdon 和 Larry Constantine 在 Structured Design 中定義了兩個關鍵的耦合度量:
- Afferent coupling(傳入耦合):指向某個程式碼元件的傳入連接數量
- Efferent coupling(傳出耦合):某個程式碼元件的傳出連接數量
在拆解單體架構時,這兩個度量特別有價值。例如,架構師會發現像
Address這類共享類別被大量元件依賴——在拆分時必須決定如何處理這些共用資產。
大多數平台都有工具可以分析程式碼的耦合特性,提供類別和元件關係的矩陣視圖(如 JDepend)。

Figure 4.2: JDepend in Eclipse analysis view of coupling relationships
Abstractness and Instability#
Robert Martin 提出的兩個衡量程式碼庫內部特性的指標:
- Abstractness(抽象度):抽象元素(abstract classes、interfaces 等)與具體元素(implementation classes)的比率
- 公式:
A = Sum(m^a) / (Sum(m^c) + Sum(m^a)) - 反映程式碼中抽象與實作的平衡
- 公式:
- Instability(不穩定度):傳出耦合占總耦合(傳出 + 傳入)的比率
- 公式:
I = C^e / (C^e + C^a) - 高不穩定度的程式碼在被修改時更容易影響其他部分
I接近 1 表示高度不穩定;接近 0 可能是 stable(多為抽象元素)或 rigid(多為具體元素)
- 公式:
Distance from the Main Sequence#
距主序線的距離是基於 abstractness 和 instability 的綜合指標:
- 公式:
D = |A + I - 1| - 在 abstractness (A) 與 instability (I) 的座標圖中,存在一條理想化的主序線(main sequence)
- 元件越接近主序線,表示抽象度與不穩定度之間的平衡越好
- 偏離主序線的兩個危險區域:
- Zone of uselessness(無用區,右上角):過度抽象,難以使用
- Zone of pain(痛苦區,左下角):過多實作、不夠抽象,脆弱且難以維護

Figure 4.3: Normalized distance from the main sequence for a particular component
如果一個程式碼庫中大多數元件都落在主序線附近,表示內部結構良好,適合進行分解。若大量元件落在無用區或痛苦區,則在嘗試重構前應先改善內部結構。
Component-Based Decomposition#
元件式分解是一種漸進式的提取方法,透過重構模式來精煉和提取單體應用中的元件(component),最終形成分散式架構。
- Component 的定義:應用程式的一個建構單元,具有明確定義的角色、職責和操作集合,通常透過命名空間或目錄結構實現

Figure 4.5: The directory structure of a codebase becomes the namespace
- 目標是從元件出發建立服務,而非從個別類別出發
將單體應用遷移為分散式架構時,應以元件為單位建構服務,而非以個別類別為單位。
Component-based decomposition 的遷移路徑:
- 通常先遷移至 service-based architecture(粗粒度的領域服務),可作為最終目標或通往微服務的跳板
- 優勢:
- 允許架構師判斷哪些領域需要更細的微服務粒度,哪些可保持粗粒度
- 不需要拆分資料庫——可先專注於領域和功能分區
- 不需要容器化或運營自動化——可使用與原應用相同的部署方式
- 屬於技術性遷移,不涉及組織結構或測試環境的變更

Figure 4.6: Extracting a part of a system

Figure 4.7: Deleting what's not wanted is another way to isolate parts of a system
適用條件#
- 程式碼庫具有可辨識的元件邊界和良好的內部結構
Tactical Forking#
戰術分叉是由 Fausto De La Torre 命名的一種務實方法,特別適用於程式碼庫是大泥球(big ball of mud)且缺乏內部結構的情況。
核心概念#
- 傳統思維是提取(extraction)需要的部分——但在高耦合的程式碼庫中,提取一部分會拉出大量依賴
- 戰術分叉的思路相反:複製整個程式碼庫,然後刪除不需要的部分
- 刪除不需要的程式碼比提取需要的程式碼更容易——可以透過編譯和簡單測試來驗證
步驟#

Figure 4.8: Before restructuring, a monolith includes several parts
- 複製(clone)整個單體應用給每個團隊

Figure 4.9: Step one clones the monolith
- 每個團隊開始刪除不屬於其負責領域的程式碼

Figure 4.10: Teams constantly refactor to remove unwanted code
- 逐步隔離目標功能,持續清理不需要的部分
- 最終產出多個粗粒度的服務

Figure 4.11: The end state of tactical forking features two services
Trade-Offs#
優點:
- 團隊可以立即開始,幾乎不需要前期分析
- 開發者更容易刪除程式碼而非提取——高耦合程式碼的提取會引發連鎖依賴問題
缺點:
- 產出的服務可能仍包含大量殘留的無用程式碼
- 除非額外清理,新服務內部的程式碼品質不會比原來的泥球好——只是少了一些
- 不同服務間的共用程式碼和元件可能出現命名不一致的問題
Tactical forking 的名稱恰如其分——它提供的是戰術性而非戰略性的重構方法,適合需要快速遷移重要或關鍵系統的場景。
Sysops Squad Saga: Choosing a Decomposition Approach#
Addison 和 Austen 使用 abstractness 和 instability 指標分析了 Sysops Squad 應用,發現大部分程式碼沿著主序線分布,確認程式碼庫適合分解。
最終選擇了 component-based decomposition,原因如下:
- 應用已具有良好定義的元件邊界,適合此方法
- 減少每個服務中保留重複程式碼的可能性
- 面對可靠性、可用性、可擴展性和工作流方面的問題,元件式分解提供更安全且可控的漸進遷移
- 服務定義會透過元件分組自然浮現,無需預先知道要建立多少個分叉應用
- 允許團隊協作識別共享功能、元件邊界和領域邊界
代價:遷移時間可能比戰術分叉更長,但團隊認為這個 trade-off 是值得的。
Addison 撰寫了一份 ADR 記錄此決定,確認採用 component-based decomposition 來遷移 Sysops Squad 單體應用到分散式架構。