golang-uber-dig

golang-uber-dig

熱門

使用 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` 技能。

2261星標
150分支
更新於 2026/6/6
SKILL.md
readonlyread-only
name
golang-uber-dig
description

使用 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 基本元件(ProvideInvokeIn/Out 結構體、命名值、值群組)完全相同。fx.In/fx.Outdig.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

最佳實踐

  1. 將容器保留在組合根 — 永遠不要將 *dig.Container 作為參數傳遞;將其視為 main() 的管道細節。服務定位器模式會破壞 DI 的可測試性優勢。
  2. 依賴介面而非具體型別 — 讓你在測試中無需修改生產程式碼即可切換實作,並允許你使用 dig.As 從寬結構體暴露狹窄介面。
  3. 一旦建構函式有 4 個以上依賴項,優先使用參數物件(dig.In 結構體)— 呼叫端保持可讀性,新增依賴項只需一行變更,而非簽章破壞。
  4. 按模組分組註冊(每個檔案一個模組,為其型別呼叫 c.Provide)— 審查與重構變成每個模組的關注點,之後你可以將模組提取為 fx.Module 而無需重寫組合。
  5. 在測試中積極驗證圖形 — 在 CI 中對組合根呼叫 c.Invoke,以便在啟動時(而非首次請求時)發現缺失的提供者。DryRun(true) 跳過建構函式執行。
  6. 從建構函式回傳錯誤而非恐慌 — 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 開啟議題。