7.7 composition root 與依賴組裝
composition root 的核心責任是集中建立具體依賴。domain 與 application 應依賴 port;main 或啟動層負責讀取 config、建立 adapter、組裝 usecase、註冊 handler 與啟動 server。
本章目標
學完本章後,你將能夠:
- 理解 composition root 為什麼要集中在啟動層
- 分辨 port、adapter 與 usecase 的組裝責任
- 用 typed config 讓 wiring 依賴可讀、可測、可替換
- 看懂哪些依賴應在
main組裝,哪些不該散在 handler 裡 - 讓啟動層只負責「把系統接起來」,不負責業務規則
【觀察】composition root 是整個應用的接線板
composition root 的核心用途是把具體依賴集中在一個地方建立。這個地方通常是 main()、cmd/.../main.go 或啟動層 package。
當讀者打開入口程式時,應該能直接看到:
- config 從哪裡來
- repository 怎麼建立
- publisher / worker / server 怎麼串起來
- 哪些 dependency 是 mockable port
- 哪些是明確的外部 adapter
這種集中式 wiring 的好處是:
- 依賴方向清楚
- 測試替身好替換
- 啟動問題容易定位
- 不會把建構邏輯散落到各個 handler 或 usecase
【判讀】dependency injection 的重點是方向
Go 的依賴注入通常不需要框架。真正的重點是:高層只依賴 port,低層在入口被組裝進來。
例如:
1type App struct {
2 jobs JobRepository
3 log EventLog
4}
5
6func NewApp(jobs JobRepository, log EventLog) *App {
7 return &App{jobs: jobs, log: log}
8}main() 負責建立具體實作,再傳給 NewApp:
1func main() {
2 cfg := LoadConfig()
3
4 repo := NewSQLJobRepository(cfg.DatabaseDSN)
5 eventLog := NewRedisEventLog(cfg.RedisAddr)
6 app := NewApp(repo, eventLog)
7
8 server := NewHTTPServer(app)
9 log.Fatal(server.ListenAndServe())
10}這裡沒有框架,但依賴方向已經清楚:App 不知道 SQL 或 Redis 是怎麼接的。
【策略】typed config 先收斂設定,再進行組裝
composition root 會變亂,通常是因為設定沒有先整理成型別清楚的 config。把環境變數、flag 與預設值先集中讀成結構體,wiring 會清楚很多。
1type Config struct {
2 HTTPAddr string
3 DatabaseDSN string
4 RedisAddr string
5}load config 的責任是把外部輸入變成可預期的程式設定,而不是在每個 adapter 初始化時各自讀環境變數。
【執行】建立 adapter 後再注入 usecase
常見的組裝順序是:
- 讀 config。
- 建立 logger / metrics / tracer。
- 建立 database / cache / broker client。
- 建立 repository 與 service。
- 建立 handler 或 server。
- 啟動背景 worker 與 HTTP server。
這樣做可以讓初始化失敗在入口層就被看見,不會等到請求進來才爆。
【判讀】組裝邏輯應集中在入口層
如果 handler 自己 new repository、new client、new worker,就會出現這些問題:
- 測試無法替換依賴
- 生命週期很難控制
- 每個 request 都可能建立不必要的資源
- 啟動路徑與請求路徑混在一起
handler 應該只接收已組裝好的依賴,專心處理輸入和回應。
【延伸】Backend 教材負責具體外部服務語意
Go 章節只需要知道依賴怎麼接,真正的外部服務語意留給 Backend 教材:
- database client 建立、pool 與 transaction 語意
- Redis client、pipeline 與 cache 邊界
- broker connection、[durable queue](/go/backend/knowledge-cards/durable-queue) 與重試
- platform secret、runtime limit 與部署環境
Go 的 composition root 不需要重複教這些技術,只要把它們正確接上即可。
與 Backend 教材的分工
本章處理 Go 程式如何組裝依賴。資料庫連線池、Redis client、broker connection、container secret 與平台設定會放在 Backend 對應模組;Go 章節只保留「誰依賴誰」與「在哪裡組裝」的設計。