Component-Based Decomposition Patterns#

Component-based decomposition 是在程式碼庫具有一定結構(透過 namespace 或目錄組織)時,用來拆解單體應用的高效技術。本章介紹一組 component-based decomposition patterns,透過重構單體原始碼,達到一組定義良好的元件,最終成為獨立服務。

這些模式按順序應用,形成從單體到分散式架構的遷移路線圖:

  1. Identify and Size Components - 識別並調整元件大小
  2. Gather Common Domain Components - 收集共用領域元件
  3. Flatten Components - 扁平化元件
  4. Determine Component Dependencies - 判斷元件依賴
  5. Create Component Domains - 建立元件領域
  6. Create Domain Services - 建立領域服務

每個模式包含三個部分:Pattern Description(模式描述)、Fitness Functions for Governance(治理的適應度函式)、以及 Sysops Squad Saga(實際案例應用)。

Figure 5.1: Component-based decomposition pattern flow and usage

Architecture Stories 與 user stories 不同,architecture story 描述影響應用程式整體結構的特定程式碼重構,並滿足某種業務驅動力(如提升可擴展性、縮短上市時間等)。


Identify and Size Components Pattern#

Pattern Description#

  • 這是拆解單體應用時第一個應用的模式,目的是識別並盤點應用程式中的架構元件(architectural components),然後適當地調整其大小
  • 元件(component) 定義為應用程式的建構模塊,擁有明確的角色、職責和一組定義良好的操作
  • 元件大多透過 namespace(命名空間)或目錄結構來呈現
  • 元件大小的衡量不應以原始碼檔案數量或類別數量為標準,而應以 statements(陳述式)的總數為指標
  • 各元件的大小應維持相對一致,落在平均值的一到兩個標準差之間

元件盤點需要收集的關鍵資訊:

  • Component name - 元件的描述性名稱,在整個應用中保持一致
  • Component namespace - 元件的物理或邏輯識別,如 ss.billing.payment
  • Percent - 元件佔整體程式碼的百分比
  • Statements - 元件中所有原始碼檔案的陳述式總數
  • Files - 元件包含的原始碼檔案總數

調整過大元件的方法:

  • 使用 functional decomposition(功能分解)方法拆分
  • 或使用 domain-driven approach(領域驅動方法)識別大元件中可能存在的子領域
  • 若大元件中找不到明確的子領域,則保留原樣

Fitness Functions for Governance#

  • Maintain component inventory - 透過 CI/CD pipeline 在部署時觸發,追蹤元件的新增或移除
  • No component shall exceed <some percent> of the overall codebase - 確保沒有元件超過整體程式碼的某個百分比閾值
  • No component shall exceed <some number> of standard deviations from the mean component size - 使用標準差識別大小異常的元件

Sysops Squad Saga: Sizing Components#

  • Addison 分析了 Sysops Squad 所有元件,發現 Reporting 元件佔了整體程式碼的 33%,明顯過大

Figure 5.2: The Reporting component is too big and should be broken apart

  • 將 Reporting 拆分為四個元件:Reporting SharedTicket ReportsExpert ReportsFinancial Reports

Figure 5.3: The large Reporting component broken into smaller reporting components

  • 拆分後所有元件的大小分佈變得更加均勻

Gather Common Domain Components Pattern#

Pattern Description#

  • 此模式用於識別和收集共用領域邏輯(common domain logic),並集中到單一元件中
  • 領域功能(domain functionality)基礎設施功能(infrastructure functionality) 不同:
    • 領域功能:業務處理邏輯的一部分,如通知、資料格式化、資料驗證,僅部分流程會用到
    • 基礎設施功能:操作性質的功能,如日誌、指標收集、安全性,所有流程都會用到
  • 合併共用領域功能可消除重複服務,減少分散式架構中潛在的重複服務數量

找到共用領域功能的方法:

  • 在元件之間尋找共用類別共同的繼承結構
  • 透過元件名稱或 namespace 的末端節點名稱相同來識別(如多個元件都有 .audit 結尾的 namespace)
  • 共用領域功能可以合併為共用服務共用函式庫(shared library)

Fitness Functions for Governance#

  • Find common names in leaf nodes of component namespace - 在 CI/CD pipeline 中定位 namespace 末端節點相同的元件,提醒架構師檢查是否為共用領域邏輯
  • Find common code across components - 定位在多個 namespace 之間使用的共同類別,用排除清單減少誤報

Sysops Squad Saga: Gathering Common Components#

  • Addison 發現三個元件都與通知客戶相關:Customer Notification (ss.customer.notification)、Ticket Notify (ss.ticket.notify)、Survey Notify (ss.survey.notify)

Figure 5.4: Notification functionality is duplicated throughout the application

  • 這三個元件的原始碼非常相似,考慮合併為單一通知元件
  • 但需權衡合併後是否會增加元件間的耦合度

Figure 5.5: Notification functionality is consolidated into a new single component


Flatten Components Pattern#

Pattern Description#

  • 此模式用於收合或展開領域、子領域和元件,確保原始碼檔案只存在於定義良好的元件中
  • 目標是讓元件命名空間成為 namespace 的葉節點(leaf node)
  • 兩種常見問題:
    • Orphaned classes(孤兒類別):原始碼檔案存在於元件 namespace 之外的中間節點,需要移入適當的元件中
    • Components within components(元件中的元件):元件巢狀在其他元件中,可能需要收合(collapse)或展開(expand)

Figure 5.6: Components, root namespaces, and orphaned classes

扁平化的目標是確保所有原始碼都歸屬於某個明確的元件,沒有「無主」的程式碼散落在中間層級。

Figure 5.7: Survey is flattened by moving the survey template code into the .survey namespace

Figure 5.8: Survey is flattened by moving the orphaned classes to new leaf nodes

Fitness Functions for Governance#

  • No orphan source code files allowed in components - 透過 CI/CD pipeline 確保所有原始碼檔案都位於元件的 namespace(葉節點)內,而非中間節點
  • 可使用 ArchUnit 等工具實現自動化驗證

Figure 5.9: Shared code in .survey is considered orphaned classes and should be moved

Figure 5.10: Shared survey code is moved into its own component

Sysops Squad Saga: Flattening Components#

  • Addison 檢查了各元件的 namespace,將散落在中間節點的原始碼檔案移入適當的元件

Figure 5.11: The Survey and Ticket components contain orphaned classes and should be flattened

Figure 5.12: The Survey component was flattened into a single component, whereas Ticket was split

  • 這個過程確保了每個元件的邊界清晰明確

Determine Component Dependencies Pattern#

Pattern Description#

  • 此模式用於識別元件之間的依賴關係,釐清這些依賴,並判斷從單體遷移到分散式架構的可行性和工作量
  • 分析依賴時需同時考慮:
    • Afferent coupling (CA) - 傳入耦合:其他元件依賴此元件的數量
    • Efferent coupling (CE) - 傳出耦合:此元件依賴其他元件的數量
    • Total coupling (CT) - 總耦合 = CA + CE
  • 元件耦合度是決定遷移成功與否的最重要因素之一
  • 可透過拆分高耦合元件來降低耦合度

可視化依賴矩陣能回答三個關鍵問題:

  1. 是否可行拆解現有單體應用?
  2. 遷移的大概工作量?
  3. 這是程式碼重構還是完全改寫?

Figure 5.13: A monolithic application with minimal component dependencies takes less effort

Figure 5.14: A monolithic application with a high number of component dependencies

Figure 5.15: A monolithic application with too many component dependencies is not a good candidate

Fitness Functions for Governance#

  • No component shall have more than <some number> of total dependencies - 限制任何元件的總耦合度不超過某個閾值(如 15)
  • <some component> should not have a dependency on <another component> - 限制特定元件之間不能有依賴關係,可使用 ArchUnit 實現

Sysops Squad Saga: Identifying Component Dependencies#

  • Addison 使用 IDE 外掛生成元件依賴圖,初始看到很多依賴關係

Figure 5.16: Component dependencies in the Sysops Squad application

  • 進一步分析後發現 Notification 元件依賴最多(因為是共用元件,這很正常)
  • TicketingReporting 領域內有很多依賴,但這些主要是共用程式碼(interfaces、helper classes)的編譯期類別引用,可作為共用函式庫處理
  • 過濾掉共用元件後,核心功能間的依賴相當少,Sysops Squad 適合拆解為分散式架構

Figure 5.17: Component dependencies in the Sysops Squad application without shared infrastructure


Create Component Domains Pattern#

Pattern Description#

  • 此模式用於將元件邏輯分組為領域(domains),使拆解時能建立更粗粒度的領域服務
  • 服務與元件的關係通常是一對多:一個服務可能包含一個或多個元件
  • 元件領域透過 namespace 的層級結構來呈現:
    • ss.customer = domain
    • ss.customer.billing = subdomain
    • ss.customer.billing.payment = component

重構步驟:

  • 將相關元件的 namespace 重構為統一的領域前綴
  • 例如:將 ss.billing.payment 改為 ss.customer.billing.payment,使其歸屬於 Customer 領域

Figure 5.18: Component domains are identified through the namespace nodes

Fitness Functions for Governance#

  • All namespaces under <root namespace node> should be restricted to <list of domains> - 限制應用程式中只能存在核准的領域,防止開發團隊無意間建立新領域
  • 可使用 ArchUnit 驗證

Sysops Squad Saga: Creating Component Domains#

Addison 與產品負責人 Parker 協商後,識別出五個主要領域

  • Ticketing (ss.ticket) - 所有工單相關功能,包括工單處理、客戶調查、知識庫
  • Reporting (ss.reporting) - 所有報表功能
  • Customer (ss.customer) - 客戶資料、帳務、支援合約
  • Admin (ss.admin) - 使用者維護和 Sysops Squad 專家
  • Shared (ss.shared) - 登入和通知功能

各領域需要進行的 namespace 重構包括:

  • Ticket 領域:將 ss.kb.maintenance 改為 ss.ticket.kb.maintenancess.survey 改為 ss.ticket.survey
  • Customer 領域:將 ss.billing.payment 改為 ss.customer.billing.paymentss.supportcontract 改為 ss.customer.supportcontract
  • Admin/Shared 領域:將 ss.expert.profile 改為 ss.admin.expertsss.login 改為 ss.shared.login

Figure 5.19: The five domains identified within the Sysops Squad application


Create Domain Services Pattern#

Pattern Description#

  • 前面的模式在單體內建立了定義良好的元件領域,此模式則將這些領域提取為獨立部署的領域服務(domain services)
  • 這就形成了 service-based architecture(服務導向架構):
    • 由使用者介面遠端存取粗粒度的領域服務
    • 所有服務共享單一的單體資料庫
  • 先遷移到 service-based architecture 的好處:
    • 讓架構師和開發團隊能逐步學習每個領域服務是否需要進一步拆分為微服務
    • 不需要拆解資料庫
    • 不需要操作自動化或容器化
    • 遷移本質上是技術性的,不涉及組織結構變更

Figure 5.20: The basic topology for a service-based architecture

不要在所有元件領域都完成識別和重構之前就套用此模式。先完成所有元件的對齊和重構,再開始將元件領域遷移為領域服務,可減少後續修改的工作量。

Fitness Functions for Governance#

  • All components in <some domain service> should start with the same namespace - 確保領域服務內的所有元件 namespace 保持一致
  • 例如:Ticket 領域服務中的所有元件都應以 ss.ticket 開頭

Sysops Squad Saga: Creating Domain Services#

  • Addison 與開發團隊合作,制定從元件領域到領域服務的遷移計劃
  • 需要將每個元件領域的程式碼從單體中提取到新的專案工作區,同時修改使用者介面以遠端存取各領域的功能

Figure 5.21: Component domains are moved to external domain services

  • 最終形成五個獨立部署的領域服務:
    • Customer domain service - Customer Profile、Support Contracts、Billing Payments、Billing History
    • Admin domain service - Expert Profile、User Maintenance
    • Shared domain service - Login、Notification
    • Ticket domain service - Ticket Maintenance、Ticket Shared、Ticket Completion、Ticket Assign、Ticket Route、Survey、KB Maintenance、KB Search
    • Reporting domain service - Reporting Shared、Financial Reports、Ticket Reports、Expert Reports

Figure 5.22: Separately deployed domain services result in a distributed Sysops Squad application


Summary#

  • 「隨性」的遷移方式很少能產生正面結果
  • 套用這些 component-based decomposition patterns 提供了一個結構化、受控且漸進式的方法來拆解單體架構
  • 完成這些模式後,團隊可以進一步:
    • 分解單體資料(見 Chapter 6)
    • 將領域服務拆分為更細粒度的微服務(見 Chapter 7)