第二種耦合是建置期耦合(build-time coupling)——一種發生在「建置專案的子專案之間」的關係。建置專案來自第 3 章的 build 視角,把一或多個子領域的原始碼組織為子專案(subproject),負責建置與測試元件。

例如 FTGO 巨石的建置專案可能包含 order-managementcustomer-managementdelivery-management 等子專案。如果修改 customer-management 會迫使 order-management 重新編譯與重新測試(即使後者沒改),代表 order-managementcustomer-management 是建置期耦合的。

設計期耦合決定「修改有多容易」;建置期耦合決定「修改後等回饋等多久」。理想上,只有被改動的子專案需要重新編譯與測試;緊建置期耦合會增加重新編譯與測試的範圍,最壞情況下開發者得等整個元件重建。

Gradle 中的建置期耦合#

實例以 Gradle 為主(其他建置工具大同小異)。

  • Gradle 建置專案通常由多個子專案組成,並透過依賴宣告(apiimplementation)指定子專案間的關係。
  • 因為 Gradle 支援增量建置(incremental build),只會重建受影響的子專案——也就是被改動的子專案,以及那些(直接或遞移)依賴它的子專案。

範例:假設 FTGO 有以下 Gradle 結構,accountingcustomer-management 都以 implementation 方式依賴 money,order-management 又透過 customer-managementapi 依賴傳遞依賴到 money

Figure 4.9: Dependencies between Gradle projects create build-time coupling (money, accounting, customer-management, order-management)

執行 ./gradlew build:

  • 修改 money 內的 Money 類別 → 會重跑 moneyaccountingcustomer-managementorder-management(透過 api 傳遞依賴)的測試。
  • 修改 customer-management 內的 Customer → 只會重跑 customer-managementorder-management,因為 accountingmoney 不依賴 customer-management

apiimplementation 的差異:api 依賴會被傳遞給上層消費者;implementation 不會。把不必對外暴露的依賴宣告為 implementation,可以切斷不必要的傳遞建置期耦合,大幅縮小重建範圍。

一個專案應該只與「穩定」的專案建置期耦合#

加速建置的一個核心策略:讓頻繁變動的子專案,不被太多其他子專案依賴

拆分 API 與 Implementation#

子領域的實作通常變動比 API 頻繁很多,因此把子領域拆成兩個 Gradle 專案:

  • API 子專案:給 client 使用的介面與相關類別。
  • Implementation 子專案:實作細節。

範例:

  • 不分拆時:單一 customer-management 專案,client(如 order-management)直接依賴它,每次實作改動都會強制 client 重建
  • 分拆後:
    • customer-management-api(穩定):放 CustomerService 介面與相關 DTO。
    • customer-management-impl(較頻繁變動):放實作類別。
    • order-management 改為依賴 customer-management-api;測試使用 CustomerService 的 mock。
  • 效果:Customer Management 內部實作改動不會頻繁觸發 client 重建,顯著縮短回饋時間。

Figure 4.10: Defining an API project + implementation project reduces build-time coupling

在子專案內部減少建置期耦合#

Gradle 子專案對自己也是建置期耦合的——子專案內任何原始檔變動都會重跑該子專案的所有測試,即使被改的程式碼與某些測試完全無關。

加速做法:把單一子專案再拆成多個更小的子專案

範例:customer-management-impl 內含 webpersistencedomain 三個套件,且 webpersistence 都依賴 domain,但彼此不依賴。

  • 不分拆:改 web 內的類別,Gradle 會重跑全部三個套件的測試,即使 domainpersistence 完全沒被影響。
  • 分拆:把三個套件改為三個子專案,webpersistence 都依賴 domain彼此不依賴——改 web 不會觸發 persistence 的測試。

Figure 4.11: Splitting a subdomain's implementation project into several projects can further reduce build-time coupling

這個分拆對速度的提升特別顯著,因為 persistence 的測試常常用實體資料庫,本身就慢。讓不相關的測試彼此獨立,是降低部署管線時間的關鍵手法

巨石架構與微服務架構的差異#

  • 大型巨石:由於只有單一程式碼庫,最小化建置期耦合至關重要;第 6 章會介紹相應的設計模式。
  • 微服務:每個服務本就相對小,建置期耦合的影響相對小,但仍有意義;第 13 章會介紹組織服務內部程式碼的模式,部分動機就是減少建置期耦合。