使用 uber-go/dig 在 Golang 中實作依賴注入 — 基於反射的容器、Provide/Invoke、dig.In/dig.Out 參數與結果物件、命名值、值群組、可選依賴、作用域與 Decorate。適用於使用或採用 uber-go/dig 時、程式碼匯入 `go.uber.org/dig` 時,或在啟動時組合應用程式圖形時。如需更高層級的生命週期與模組,請參閱 `samber/cc-skills-golang@golang-uber-fx` 技能。
角色設定: 你是一位使用 dig 組合應用程式圖形的 Go 架構師。你將容器保留在組合根(composition root),依賴介面而非具體型別,並將建構函式的錯誤視為第一級失敗。
在 Go 中使用 uber-go/dig 進行依賴注入
基於反射的 DI 工具組,專為驅動應用程式框架而設計(它是 uber-go/fx 的底層引擎),並在啟動時解析物件圖形。
官方資源:
此技能並非詳盡無遺。請參閱函式庫文件與程式碼範例以取得更多資訊。Context7 可作為探索平台提供協助。如需 Go 套件文件、版本、符號與已知漏洞,→ 請參閱 samber/cc-skills-golang@golang-pkg-go-dev 技能。
go get go.uber.org/dig
dig 與 fx 的比較
fx 建構於 dig 之上,並共用相同的容器引擎 — DI 基本元件(Provide、Invoke、In/Out 結構體、命名值、值群組)完全相同。fx.In/fx.Out 是 dig.In/dig.Out 的重新匯出。
fx 在 dig 之上新增的功能:
| 關注點 | dig | fx |
|---|---|---|
| DI 容器 | ✅ dig.New() |
✅(內嵌) |
| 生命週期鉤子 | ❌ | ✅ fx.Lifecycle OnStart/OnStop |
| 模組系統 | ❌ | ✅ fx.Module 搭配作用域裝飾器 |
| 訊號感知執行迴圈 | ❌ | ✅ app.Run() 阻塞於 SIGINT/SIGTERM |
| 結構化事件日誌 | ❌ | ✅ fx.WithLogger / fxevent |
| 啟動/停止逾時 | ❌ | ✅ fx.StartTimeout / fx.StopTimeout |
選擇 dig 當你只需要組合圖形時:CLI 工具、向呼叫者暴露容器的函式庫、測試框架,或將 DI 嵌入到管理自身生命週期的現有應用程式中。
選擇 fx 用於長時間執行的服務(HTTP 伺服器、工作者、守護程式)— 生命週期與訊號處理在那裡是不可妥協的。請參閱 samber/cc-skills-golang@golang-uber-fx 技能。
容器
import "go.uber.org/dig"
c := dig.New()
有用的選項:dig.DeferAcyclicVerification()(更快的啟動速度)、dig.RecoverFromPanics()(將恐慌轉換為 dig.PanicError)、dig.DryRun(true)(驗證但不執行)。
Provide 與 Invoke
// 註冊建構函式 — 惰性,僅在需要其輸出時執行
err := c.Provide(func(cfg *Config) (*sql.DB, error) {
return sql.Open("postgres", cfg.DSN)
})
// 透過將服務作為函式參數請求,從容器中取出服務
err = c.Invoke(func(db *sql.DB) error {
return db.Ping()
})
建構函式是惰性且記憶化的:每個輸出型別只建構一次並共用(每個容器單例)。Provide 在建構函式格式錯誤時於註冊時回傳錯誤;Invoke 回傳建構函式的錯誤,並附帶觸發該錯誤的依賴路徑。
dig 的建構函式可以是任何函式。輸入是依賴項,輸出是提供的型別。error(最後一個回傳值)表示建構失敗。遵循「接受介面,回傳結構體」。
使用 dig.In 的參數物件
當建構函式有 4 個以上依賴項時,嵌入 dig.In 將它們分組為結構體欄位並標記欄位:
type HandlerParams struct {
dig.In
Logger *zap.Logger
DB *sql.DB
Cache *redis.Client `optional:"true"` // 若未提供則為零值
DBRO *sql.DB `name:"readonly"` // 命名依賴
Routes []http.Handler `group:"routes"` // 值群組
}
func NewHandler(p HandlerParams) *Handler { /* ... */ }
標籤:name:"..."、optional:"true"、group:"..."。
使用 dig.Out 的結果物件
從一個建構函式回傳多個值,並為結果附加 name/group 標籤:
type ConnResult struct {
dig.Out
ReadWrite *sql.DB `name:"primary"`
ReadOnly *sql.DB `name:"readonly"`
}
func NewConnections(cfg *Config) (ConnResult, error) { /* ... */ }
命名值
相同型別的兩個提供者會衝突。使用 dig.Name 消除歧義:
c.Provide(NewPrimaryDB, dig.Name("primary"))
c.Provide(NewReadOnlyDB, dig.Name("readonly"))
透過在 dig.In 欄位中加入 name:"primary" / name:"readonly" 來消費。
值群組
多個提供者,一個消費者切片 — 典型用於 HTTP 處理器、健康檢查、遷移:
type RouteResult struct {
dig.Out
Handler http.Handler `group:"routes"`
}
func NewUserHandler(db *sql.DB) RouteResult { /* ... */ }
func NewPostHandler(db *sql.DB) RouteResult { /* ... */ }
type ServerParams struct {
dig.In
Routes []http.Handler `group:"routes"`
}
扁平化 — 附加 ,flatten(例如 group:"routes,flatten")以展開切片而非巢狀化。群組順序不保證;如果順序重要,請從單一建構函式提供明確的有序切片。
以介面提供 (dig.As)
註冊具體建構函式,並將其暴露為一個或多個介面,無需單獨的轉接器:
c.Provide(NewPostgresDB, dig.As(new(Database), new(io.Closer)))
// 消費者請求 Database 或 io.Closer;*PostgresDB 保持隱藏。
完整應用範例
func main() {
c := dig.New()
must(c.Provide(NewConfig))
must(c.Provide(NewLogger))
must(c.Provide(NewDatabase))
must(c.Provide(NewServer))
err := c.Invoke(func(srv *http.Server) error {
return srv.ListenAndServe()
})
if err != nil {
log.Fatal(err)
}
}
func must(err error) { if err != nil { panic(err) } }
dig 沒有內建的生命週期。如果你需要 OnStart/OnStop 鉤子、訊號處理與優雅關閉,請使用 fx — 參閱 samber/cc-skills-golang@golang-uber-fx 技能。
關於 Decorate、Scopes、可選依賴、錯誤輔助工具與 Visualize,請參閱 advanced.md。
最佳實踐
- 將容器保留在組合根 — 永遠不要將
*dig.Container作為參數傳遞;將其視為main()的管道細節。服務定位器模式會破壞 DI 的可測試性優勢。 - 依賴介面而非具體型別 — 讓你在測試中無需修改生產程式碼即可切換實作,並允許你使用
dig.As從寬結構體暴露狹窄介面。 - 一旦建構函式有 4 個以上依賴項,優先使用參數物件(
dig.In結構體)— 呼叫端保持可讀性,新增依賴項只需一行變更,而非簽章破壞。 - 按模組分組註冊(每個檔案一個模組,為其型別呼叫
c.Provide)— 審查與重構變成每個模組的關注點,之後你可以將模組提取為 fx.Module 而無需重寫組合。 - 在測試中積極驗證圖形 — 在 CI 中對組合根呼叫
c.Invoke,以便在啟動時(而非首次請求時)發現缺失的提供者。DryRun(true)跳過建構函式執行。 - 從建構函式回傳錯誤而非恐慌 — dig 會用依賴路徑包裹它們,使失敗點變得明顯。
常見錯誤
| 錯誤 | 修正 |
|---|---|
| 將容器傳入服務 | 容器屬於 main()。注入服務所需的型別化依賴;否則測試需要建立真實容器。 |
相同型別有兩個提供者但未使用 Name |
dig 在 Provide 時會報錯。要么命名它們,要么合併為一個回傳 dig.Out 結果結構體的提供者。 |
忽略 Provide 錯誤 |
用 must 輔助函式包裹每個 Provide。無聲的註冊錯誤會變成更晚出現的遺失型別錯誤。 |
| 在順序重要時使用群組 | 群組是無序的。如果順序重要(中介軟體鏈、遷移順序),請用一個建構函式提供明確的有序切片。 |
| 建構函式在匯入時有副作用 | 保持 init() 為空 — 只在建構函式內部、圖形建立後才開始工作。 |
測試
dig 容器很便宜 — 每個測試建立一個新的,使用 Decorate 覆蓋提供者,並呼叫 Invoke 驅動系統。如需完整模式(每個測試的組合、共用輔助函式、CI 中的圖形驗證、斷言組合時錯誤、從建構函式恐慌中恢復),請參閱 testing.md。
延伸閱讀
- advanced.md — Decorate、Scopes、可選依賴、錯誤輔助工具、Visualize、完整快速參考
- recipes.md — 端到端範例:含路由群組的 HTTP 伺服器、兩個資料庫、請求作用域、裝飾器、乾執行驗證
- testing.md — 測試模式與圖形驗證
交叉參考
- → 參閱
samber/cc-skills-golang@golang-uber-fx技能以了解建構於 dig 之上的應用生命週期、模組與訊號感知 Run() - → 參閱
samber/cc-skills-golang@golang-dependency-injection技能以了解 DI 概念與函式庫比較 - → 參閱
samber/cc-skills-golang@golang-samber-do技能以了解基於泛型且無反射的替代方案 - → 參閱
samber/cc-skills-golang@golang-google-wire技能以了解編譯時 DI(無執行時容器) - → 參閱
samber/cc-skills-golang@golang-structs-interfaces技能以了解介面設計模式 - → 參閱
samber/cc-skills-golang@golang-testing技能以了解一般測試模式
如果你在 uber-go/dig 中遇到錯誤或非預期行為,請在 https://github.com/uber-go/dig/issues 開啟議題。






