golang-google-wire

golang-google-wire

熱門

在 Golang 中使用 google/wire 進行編譯時期依賴注入 — wire.NewSet、wire.Build、wire.Bind(介面→具體實作)、wire.Struct、wire.Value、wire.InterfaceValue、wire.FieldsOf、清理函式、//go:build wireinject 注入器檔案,以及產生的 wire_gen.go。適用於使用或採用 google/wire 時、程式碼庫匯入 `github.com/google/wire` 時,或透過 `wire.Build` 在編譯時期建構應用程式圖時。若需執行時期反射式 DI,請參閱 `samber/cc-skills-golang@golang-uber-dig` 技能。

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

在 Golang 中使用 google/wire 進行編譯時期依賴注入 — wire.NewSet、wire.Build、wire.Bind(介面→具體實作)、wire.Struct、wire.Value、wire.InterfaceValue、wire.FieldsOf、清理函式、//go:build wireinject 注入器檔案,以及產生的 wire_gen.go。適用於使用或採用 google/wire 時、程式碼庫匯入 `github.com/google/wire` 時,或透過 `wire.Build` 在編譯時期建構應用程式圖時。若需執行時期反射式 DI,請參閱 `samber/cc-skills-golang@golang-uber-dig` 技能。

角色: 你是一位使用 wire 進行編譯時期 DI 的 Go 架構師。你讓編譯器捕捉遺漏的依賴,將 wire_gen.go 視為已提交的原始碼,並在每次圖形變更後重新執行 wire ./...

依賴:

  • wire:go install github.com/google/wire/cmd/wire@latest

在 Go 中使用 google/wire 進行編譯時期依賴注入

程式碼生成 DI 工具包。Wire 在編譯時期解析依賴圖,並產生純 Go 建構子呼叫 — 沒有執行時期容器,沒有反射。錯誤在執行 wire ./... 時出現,而不是在第一次請求時。

注意:google/wire 已於 2025 年 8 月封存(功能完整;仍接受錯誤修正)。

官方資源: pkg.go.dev · github.com/google/wire · 使用者指南 · 最佳實踐

此技能並非詳盡無遺。請參閱函式庫文件和程式碼範例以獲取更多資訊。Context7 可作為探索平台提供協助。如需 Go 套件文件、版本、符號和已知漏洞,→ 請參閱 samber/cc-skills-golang@golang-pkg-go-dev 技能。

go get -tool github.com/google/wire/cmd/wire@latest
go get github.com/google/wire

wire 與執行時期 DI 的比較

關注點 wire dig / fx / samber/do
解析方式 編譯時期(程式碼生成) 執行時期(反射)
錯誤偵測 wire ./... 失敗 首次 Invoke/啟動時
執行時期容器 無 — 純 Go 呼叫 存在
生命週期鉤子 無內建 fx: OnStart/OnStop
產生的檔案 wire_gen.go(已提交)

如需生命週期、延遲載入和完整矩陣,請參閱 samber/cc-skills-golang@golang-dependency-injection

提供者

提供者是任何 Go 函式 — 輸入是依賴,輸出是提供的型別。三種回傳形式:

func NewConfig() *Config                          { return &Config{Addr: ":8080"} }
func NewDB(cfg *Config) (*sql.DB, error)          { return sql.Open("postgres", cfg.DSN) }
func NewRedis(cfg *Config) (*redis.Client, func(), error) { // 清理函式以反向順序鏈結
    c := redis.NewClient(&redis.Options{Addr: cfg.RedisAddr})
    return c, func() { c.Close() }, nil
}

提供者集合

wire.NewSet 將提供者分組以便重用。集合可以引用其他集合。

// infra/wire.go
var InfraSet = wire.NewSet(
    NewConfig,
    NewDB,
    NewRedis,
)

// service/wire.go
var ServiceSet = wire.NewSet(
    NewUserRepo,
    NewUserService,
    wire.Bind(new(UserStore), new(*UserRepo)), // 介面綁定
)

保持集合小巧:函式庫集合暴露穩定的表面(新增輸入或移除輸出會破壞下游注入器)。每個套件一個集合是實用的預設值。

注入器與 //go:build wireinject

注入器檔案宣告初始化函式。Wire 將其主體產生到 wire_gen.go 中,並取代樁程式。

//go:build wireinject

package main

import "github.com/google/wire"

// Wire 產生此函式的主體。
func InitApp() (*App, func(), error) {
    wire.Build(InfraSet, ServiceSet, NewApp)
    return nil, nil, nil // 由程式碼生成取代
}

//go:build wireinject 標籤防止樁程式被編譯進二進位檔 — 只有 wire_gen.go(沒有此標籤)能通過 go build。沒有此標籤時,兩個檔案會定義相同的函式,導致編譯錯誤。

當虛擬回傳不方便時的替代語法:

func InitApp() (*App, func(), error) {
    panic(wire.Build(InfraSet, ServiceSet, NewApp))
}

介面綁定

Wire 禁止隱含的介面滿足 — 你必須明確宣告綁定,以便在有多個型別實作相同介面時,圖形不會產生歧義。

var Set = wire.NewSet(
    NewPostgresUserRepo,
    wire.Bind(new(UserStore), new(*PostgresUserRepo)), // 告訴 wire:*PostgresUserRepo 滿足 UserStore
)

明確綁定可防止在別處新增實作相同介面的新型別時,圖形被破壞。

結構體提供者與值

wire.Struct 從圖形中填入結構體欄位,無需手動建構子。使用 wire:"-" 標記欄位以排除。

wire.Struct(new(Server), "Logger", "DB") // 注入指定欄位
wire.Struct(new(Server), "*")            // 注入所有未排除的欄位
wire.Value(Foo{X: 42})                   // 常數表達式(無函式呼叫/通道)
wire.InterfaceValue(new(io.Reader), os.Stdin) // 介面型別的字面值
wire.FieldsOf(new(Config), "DSN", "Addr")    // 將結構體欄位提升為圖形節點

請參閱 advanced.md 了解 wire:"-" 排除標籤和 wire.FieldsOf 的詳細資訊。

消除重複型別的歧義

Wire 禁止為相同型別提供兩個提供者。將底層型別包裝成不同的命名型別,使每個型別只有一個提供者:

type PrimaryDSN string
type ReplicaDSN string

完整應用程式範例

// wire.go — 注入器,透過建置標籤從二進位檔中排除
//go:build wireinject

package main

func InitApp() (*App, func(), error) {
    wire.Build(config.ConfigSet, infra.InfraSet, service.ServiceSet, NewApp)
    return nil, nil, nil
}

// main.go
func main() {
    app, cleanup, err := InitApp()
    if err != nil { log.Fatal(err) }
    defer cleanup()
    app.Run()
}

Wire 產生 wire_gen.go(純 Go,已提交,請勿編輯)。如需包含每個套件集合、大量清理圖形和產生輸出的完整範例,請參閱 recipes.md

程式碼生成工作流程

wire ./...           # 重新產生模組中的所有注入器
wire check ./...     # 驗證圖形但不重新產生(快速 CI 檢查)

每次建構子簽章變更後,執行 wire ./...。在注入器檔案中加入 //go:generate go run github.com/google/wire/cmd/wire,以便 go generate ./... 也能運作。提交 wire_gen.go — 它必須保持同步以進行 CI 建置。

最佳實踐

  1. 絕不手動編輯 wire_gen.go — 每次執行 wire ./... 時都會覆寫它。將其視為已提交的建置產物;真相來源是提供者和注入器檔案。
  2. 務必在注入器檔案中加入 //go:build wireinject — 省略它會導致重複符號的編譯錯誤,因為樁程式和產生的檔案都定義了相同的函式。
  3. 使用命名型別來區分相同底層型別的值 — wire 強制每個型別只有一個提供者;命名型別如 type DSN string 可讓 PrimaryDSNReplicaDSN 共存。
  4. 保持函式庫提供者集合最小化且向後相容 — 新增必要的輸入會破壞下游注入器;移除輸出也會。僅在同一個版本中引入新建立的型別。
  5. 從清理提供者回傳 (T, func(), error),讓 wire 鏈結它們 — wire 產生正確的反向順序清理,並處理部分失敗(如果建構中途失敗,只會執行已建構項目的清理)。
  6. 保持注入器檔案專注 — 每個檔案一個函式,一次一個套件匯入。包含數十個 wire.Build 參數的龐大注入器難以理解;委派給每個套件的集合。

常見錯誤

錯誤 修正
手動編輯 wire_gen.go 絕不編輯。修改提供者或注入器,然後重新執行 wire ./...
缺少 //go:build wireinject 將標籤作為每個注入器檔案的第一行加入。
兩個提供者回傳 *sql.DB 用命名結構體型別包裝:type PrimaryDB struct { *sql.DB } — Wire 不區分指標型別別名。
注入介面時未使用 wire.Bind 在提供者集合中加入 wire.Bind(new(MyInterface), new(*MyImpl))
變更後忘記重新執行 wire ./... go build 前執行 wire;將其加入 go generate 或 Makefile 目標。
呼叫 cleanup() 時未檢查 nil Wire 在建構錯誤時回傳 nil cleanup;使用 if cleanup != nil { defer cleanup() } 保護。

測試

Wire 產生純 Go 建構子,因此單元測試使用手動注入 — 無需複製或重設容器。如需測試模式(測試注入器將真實提供者替換為假物件、CI 檢查 wire_gen.go 是否過時),請參閱 testing.md

延伸閱讀

  • advanced.md — 清理鏈結、多個注入器、集合巢狀、錯誤目錄、程式碼生成旗標、快速參考
  • recipes.md — HTTP 伺服器、多注入器建置、大量清理圖形、CLI 嵌入
  • testing.md — 測試注入器、假綁定、CI 過時檢查

交叉參考

  • → 參閱 samber/cc-skills-golang@golang-dependency-injection 技能了解 DI 概念和函式庫比較
  • → 參閱 samber/cc-skills-golang@golang-uber-dig 技能了解無生命週期的執行時期反射式 DI
  • → 參閱 samber/cc-skills-golang@golang-uber-fx 技能了解具有生命週期鉤子、模組和訊號感知 Run() 的執行時期 DI
  • → 參閱 samber/cc-skills-golang@golang-samber-do 技能了解基於泛型且無反射的 DI
  • → 參閱 samber/cc-skills-golang@golang-structs-interfaces 技能了解介面設計模式
  • → 參閱 samber/cc-skills-golang@golang-testing 技能了解一般測試模式

如果你遇到 google/wire 的錯誤或非預期行為,請在 https://github.com/google/wire/issues 開啟問題。