設計期耦合(design-time coupling)是微服務架構的定義性特徵之一——服務之間必須鬆設計期耦合,變更才不會頻繁地擴散。重要的是,設計期耦合不只是元件視角中元件之間的關係,也存在於領域視角的類別、套件、子領域之間——這些也都必須鬆設計期耦合。
設計期耦合與內聚#
軟體設計的本質之一,是把元素組織成階層,以最小化設計期耦合;當耦合不可避免時,則用「內聚(cohesion)」原則妥善管理。
階層通常如下:
- 方法與屬性 → 類別 → 套件 → 高階套件 → 子領域 → 元件
每一層都被「最小化耦合 + 最大化內聚」這對雙生目標塑造。
設計期耦合是古老且依然關鍵的概念#
1972 年 David Parnas 的經典論文《On the criteria to be used in decomposing systems into modules》,以及 1974 年 Larry Constantine 的《Structured Design》,已經建立了 coupling 與 cohesion 的觀念。50 多年後的今天,這些概念依舊高度適用。
設計期耦合的定義:
一個軟體元素變更時,有多大機率要連帶修改另一個元素。
例子:Order Management 子領域使用 Delivery Management 安排配送,因此存在設計期耦合。關鍵不是消除這個耦合,而是讓它鬆——Delivery Management 未來的變更鮮少影響 Order Management。
內聚:讓「不可避免的緊耦合」聚在同一個父元素內#
一個元素是「內聚」的,當它的子元素彼此緊設計期耦合;不是緊設計期耦合的子元素應該歸屬於不同的父元素。
換言之,內聚的精神是:緊耦合的東西要在一起。
實作層級的應用:
- 緊耦合的類別要放在同一個套件內。
- 緊耦合的套件要放在同一個父套件內。
- 緊耦合的子領域要放在同一個元件內。
範例(FTGO 菜單演化):
- 早期
OrderLineItem與MenuItem簡單對應。隨著菜單支援大量客製化選項,LineItem與MenuItem變得緊設計期耦合。 - 第一種設計把
LineItem與MenuItem放在不同套件,即便兩者緊耦合,套件之間卻是鬆耦合,內聚度低。 - 較好的設計是把
LineItem、MenuItem與其選項放進同一個套件,這個套件對外鬆耦合、對內緊耦合——內聚度高,變更得以局部化。

Figure 4.2: Loosely coupled, internally cohesive packages localize changes (LineItems and MenuItems example)
內聚也讓「修改某元素」更輕鬆——你只需考慮這個元素內可能要一起改的子元素。
反例:不內聚的元素摻雜大量與當前變更無關的程式碼,讀懂與測試的成本都更高。如果要升級某子領域所屬元件的技術棧,而元件不內聚,升級工作會擴散到無關的子領域。
鬆設計期耦合的好處#
鬆設計期耦合減少了一次變更會影響到的元素數量,加上內聚的安排,讓真正受影響的元素多半在同一個套件或模組內。三個主要好處:
- 修改更容易、更安全
- 提升團隊自主性
- 讓組織能夠擴張
修改更容易、更安全#
範例:Order Management 透過 Customer Management 的 API 取得顧客資料。

Figure 4.3: Order Management is design-time coupled to Customer Management because it invokes its API
- 鬆耦合:對 Customer Management 的修改通常局部化在它自己內(甚至單一類別),變更小到「能塞進一個開發者的腦袋」,容易理解、容易驗證。
- 緊耦合:任何修改都會擴散到 Order Management,範圍變大、風險變高,還可能引入意料之外的副作用——若兩個模組分屬不同團隊,協調成本暴增。
提升團隊自主性#
如果緊耦合的兩個子領域分屬不同團隊,變更頻繁需要兩個團隊一起合作:
- 開會討論、規劃。
- 一個團隊等另一個團隊先做完。
- 協調與等待造成低生產力。
業務層面的證據:《Accelerate》研究發現,商業成功與軟體開發組織的效能有強烈相關;高效能組織的開發者能夠**「不需要與團隊外的人溝通協調就完成工作」、「能對系統做大規模設計變更而不需依賴或拖累其他團隊」**。能這樣工作的關鍵,正是鬆設計期耦合。
讓組織擴張#
鬆耦合架構下,通訊開銷低,加新團隊不會大幅推升整體溝通負擔。緊耦合架構則迫使團隊持續協調——多個團隊行為上反而像「一個大團隊」,擴張不順。
Fred Brooks《The Mythical Man-Month》(1975)的經典觀察:「Adding people to a late project makes it later.」(加人到延誤的專案,只會更慢。)
通訊複雜度為 O(N²),N 是團隊成員數;規模增加,通訊開銷指數級膨脹。
《Accelerate》的更新發現:
- 低效能組織加人會變慢(每位開發者每日部署次數下降)。
- 中效能組織保持不變,推估是因為鬆設計期耦合控制住了通訊開銷。
- 高效能組織加人反而更快——每位開發者每日部署次數隨人數上升。
設計鬆耦合軟體的方法#
鬆設計期耦合不會憑空出現,需要刻意的設計選擇。難點在於:你必須預測哪些概念會變、哪些會穩定。穩定的概念表現為 API,易變的細節隱藏在 API 後面。

Figure 4.4: Designing an API is making a bet about what is stable (API) vs unstable (implementation)
預測會出錯:若把易變的東西放進 API,變更會擴散到所有 client;若把穩定的東西埋進 implementation,則會白白增加維護成本。
新專案早期 API 通常不穩定,需要靠迭代摸清領域;成熟之後 API 才會慢慢穩下來。這也是「monolith-first」的好處之一——現代 IDE 的自動重構讓巨石內部的 API 迭代相對容易。
以下三條原則特別有用。
DRY(Don’t Repeat Yourself)#
「每一個知識,都應該在系統內有單一、明確、權威的表示。」
範例:FTGO 訂單總額的計算邏輯多年來相對穩定(小計 + 稅 + 配送費 + 服務費 + 小費),被 copy/paste 到許多地方。疫情期間新增 COVID 附加費,分散的實作就成了災難——必須四處找、四處改,既慢又易錯。若一開始就遵循 DRY,把它放進 OrderTotalCalculator,只需改一處。

Figure 4.5: Applying the DRY principle replaces multiple implementations with a single one (order total calculator)
套用 DRY 後,多個元素會依賴同一個實作。在巨石中沒問題,但在微服務中,呼叫可能變成昂貴的網路請求;若改放進共用函式庫,函式庫的不穩定業務邏輯升級又會迫使多個服務同步升級——降低團隊自主性。
何時可以容忍重複? 當不同實作差異不會造成 bug、且維護成本低時,例如不同服務使用同一個工具的不同版本,有限的 copy/paste 反而能換取更高的自主性。
Iceberg 原則:像冰山一樣#
軟體元素應該像冰山:水面下的實作遠大於水面上的 API。

Figure 4.6: Software elements should resemble icebergs — a small stable API hides most of the implementation
API 越小越穩定越好,因為:
- 隱藏的東西可隨意改。
- 暴露的東西難改,因為會影響 client。
John Ousterhout 在《A Philosophy of Software Design》提出等價的幾何隱喻:讓元素「深而非廣」——深度對應實作,寬度對應 API。讓元素深而窄,就是最小化暴露。
管理依賴:消費也要少#
Iceberg 原則處理「暴露」,依賴管理處理「消費」:每個依賴都是一個「未來可能要改的理由」,依賴越多,自身越不穩定。
範例:Order Management 在訂單建立時要通知多個子領域,有兩種做法:
- A. Order Management 直接呼叫每個子領域的 API:Order Management 同時依賴所有被呼叫者,任一 API 變更都會牽動 Order Management。
- B. Order Management 發布事件,子領域訂閱(Observer 模式):依賴反向,子領域依賴 Order Management;Order Management 不知道誰訂閱。

Figure 4.7: Two ways for Order Management to notify subdomains — direct API calls vs. publish/subscribe events
哪種比較好?取決於各子領域 API 的穩定度;第 20 章會深入分析這類取捨。一般而言,把依賴指向較穩定的一方通常勝出。
依賴的「表面積」也要看#
依賴的數量重要,消費了多少依賴也重要——消費得越多,改動的機率越高。
範例(NASA 風格的發射狀態檢查):
- Flight Director 對每個子系統(引擎、導引、燃料)問:「Engine, go/no go?」「Guidance, go/no go?」每個子系統內部極複雜,但只暴露
boolean isGo()這個極穩定的 API。 - 等價設計 A:Flight Management 對每個子領域呼叫
isGo()——鬆設計期耦合,各子系統怎麼改都不影響它。 - 設計 B:Flight Management 拉出每個子系統的內部資料,自己做判斷——這就是物件導向的「Insider Trading」氣味,變成緊設計期耦合,子系統一改它就跟著改。

Figure 4.8: NASA-style launch status check — boolean isGo() per subsystem vs. controller reading internal data
設計鬆耦合的關鍵心法:
- 只暴露穩定的概念(Iceberg)。
- 盡量少消費:依賴少、且消費的「表面積」小。
- DRY 適度套用,理解其在微服務中的隱藏成本。
這些原則同時提升團隊自主性與變更效率。