我們討論架構時,往往專注於高層次的元件劃分(如 UI, Interactor, Database)。
但當我們真正打開 IDE 看程式碼時,目錄結構往往又是另回事。
Simon Brown 在這章提出了一個尖銳問題:「你的目錄結構(Packaging)是否反映了你的架構設計?」
他以一個「線上書店(訂單查詢)」的使用案例為例,比較了四種主流的程式碼組織策略。
一、四種程式碼組織策略總覽#
| 打包策略 | 切分依據 | 結構範例 | 優點 | 缺點 |
|---|---|---|---|---|
| 逐層打包 (by Layer) | 技術角色(水平) | controllers/, services/, repositories/ | 簡單,好起步 | 無法尖叫;封裝洩漏 |
| 依功能打包 (by Feature) | 領域概念(垂直) | orders/, books/, customers/ | 符合尖叫架構 | 若無存取控制,只是視覺分組 |
| Ports and Adapters | 內外之分(六邊形) | domain/(內)+ infrastructure/(外) | 領域邏輯與框架解耦 | 需嚴格遵守依賴方向(外 → 內) |
| 依元件打包 (by Component) | 粗粒度元件 | OrdersComponent/(對外只有 Service 是 public) | 真正的封裝;強制經過 Service | 需嚴格控制存取修飾符 |

Figure 34.1: Package by layer

Figure 34.2: Package by feature
1. 逐層打包的問題#
- 任何一個領域的變更,幾乎都會跨越所有層;修改檔案散落在各處
- 從頂層目錄完全看不出這是訂單系統、書店還是任何業務——它只尖叫:「我是 MVC」
- 規模一大就必須再模組化,逐層切分的方式撐不起真實專案
2. 依功能打包的改善與侷限#
- 頂層目錄變成
orders/,books/——開始向領域靠近 - 但若內部類別皆為
public,外層仍能穿越封裝直接存取 Repository,達不到真正的隔離
二、Ports and Adapters:內外之分#
Martin 在前文提過的 Hexagonal Architecture、Onion Architecture 與「邊界—控制器—實體」都屬於同一家族。
它們的共同精神是:將領域邏輯(Inside)與技術細節(Outside)徹底分離。

Figure 34.3: A code base with an inside and an outside
- Inside(領域): 封裝所有業務概念,命名使用領域通用語言(Ubiquitous Language)——例如
Orders,而非OrdersRepository - Outside(基礎設施): UI、資料庫、第三方整合
- 依賴鐵律: 外部依賴內部,內部永遠不反向依賴外部

Figure 34.4: View orders use case
三、依元件打包:終極整合#
Simon Brown 在本書提出了第四種——也是他偏好的——組織方式:Package by Component。
它結合了 Package by Feature 的領域感,以及 Ports and Adapters 的封裝精神。
1. 核心思想#
將與單個粗粒度元件(如「訂單處理」)有關的所有責任綑綁到單一 package 中:
- 對外: 只有
OrdersComponent(Service 介面)是public - 對內:
OrdersRepository,OrdersController,OrdersImplementation都是package-private
這等於在單體(Monolith)內部,預先切出一個「微服務形狀」——未來要拆成獨立服務時,成本極低。

Figure 34.5: Relaxed layered architecture

Figure 34.6: View orders use case
2. 為何勝出?#
- 單一入口: 所有與訂單有關的程式碼都在
OrdersComponent內,找得到、改得到 - 強制封裝: Controller 想跨過 Service 直接存取 Repository?編譯器直接擋下
- 漸進式遷移: 從單體起步,邊界夠清楚時可無痛升級為微服務
四、魔鬼藏在細節中#
上述四種分法若所有類別都設為 public,在編譯器眼中會完全一模一樣——差別只存在於架構師的想像中。

Figure 34.7: All four architectural approaches are the same
1. 架構的幻覺#
- 若把嚴格層級圖畫得再漂亮,卻讓每個類別都
public,那只是自我催眠 - 新進開發者為了趕進度,會毫無阻礙地寫出跨層級、跳過 Service 的依賴
- 靠「Code Review + 自律」維繫架構,在交付壓力下必然失守
2. 讓編譯器當守門員#
與其仰賴紀律,不如讓語言機制強制執行:
- Java: 善用
package-private(預設存取修飾符),只把跨 package 介面標為public - C#: 利用
internal限制組件外可見性 - 靜態分析工具(NDepend、Structure101、ArchUnit) 可作為補強,但回饋迴圈比編譯器慢

Figure 34.8: Access modifier restrictions

Figure 34.9: Domain and infrastructure code
3. 組織 vs. 封裝(Organization vs. Encapsulation)#
- 「分組」(放到不同 package)只是組織,不是封裝
- 真正的封裝來自「讓編譯器拒絕不該發生的依賴」
- 好架構不是檔案放得多整齊,而是錯誤的依賴寫不出來
不要讓你的架構圖變成紙上談兵。
選擇 「依元件打包」,並用存取修飾符(Access Modifiers) 隱藏實作細節。
讓編譯器成為你的架構守門員——這就是 Simon Brown 留給我們的最後一條建議。