SKILL.md
readonlyread-only
name
golang-samber-do
description
在 Golang 中使用 samber/do 進行依賴注入 — 服務容器、生命週期管理、作用域、健康檢查、優雅關閉與模組組織。適用於使用或採用 samber/do 時、程式碼庫匯入 github.com/samber/do 或 github.com/samber/do/v2 時,或將手動建構子注入重構為 DI 容器時。
角色設定: 你是一位正在建立依賴注入的 Go 架構師。你將容器保留在組合根(composition root),依賴介面而非具體型別,並將提供者錯誤視為第一級失敗。
在 Go 中使用 samber/do 進行依賴注入
基於 Go 1.18+ 泛型的型別安全依賴注入工具組。
官方資源:
此技能並非詳盡無遺。請參考函式庫文件與程式碼範例以取得更多資訊。Context7 可作為探索平台提供協助。如需 Go 套件文件、版本、符號與已知漏洞,請參閱 samber/cc-skills-golang@golang-pkg-go-dev 技能。
請勿使用此函式庫的 v1 版本。請安裝 v2:
go get -u github.com/samber/do/v2
核心概念
注入器(容器)
import "github.com/samber/do/v2"
injector := do.New()
服務型別
- Lazy(預設):首次請求時建立
- Eager:容器啟動時立即建立
- Transient:每次請求都建立新實例
- Value:預先建立的值,無需實例化
提供者函式
服務必須透過提供者函式註冊:
type Provider[T any] func(i Injector) (T, error)
基本用法
1. 定義與註冊服務
遵循「接受介面,回傳結構」:
// 註冊服務(預設為 lazy)
do.Provide(injector, func(i do.Injector) (Database, error) {
return &PostgreSQLDatabase{connString: "postgres://..."}, nil
})
// 註冊預先建立的值
do.ProvideValue(injector, &Config{Port: 8080})
// 註冊 transient 服務(每次新實例)
do.ProvideTransient(injector, func(i do.Injector) (*Logger, error) {
return &Logger{}, nil
})
// 註冊 eager 服務(啟動時立即建立)
do.ProvideValue(injector, &Config{Port: 8080})
2. 調用服務
容器只能在組合根(composition root)存取:
// 調用並處理錯誤
db, err := do.Invoke[Database](injector)
// MustInvoke 在錯誤時 panic(確定服務存在時使用)
db := do.MustInvoke[Database](injector)
3. 服務依賴
func NewUserService(i do.Injector) (UserService, error) {
db := do.MustInvoke[Database](i)
cache := do.MustInvoke[Cache](i)
return &userService{db: db, cache: cache}, nil
}
do.Provide(injector, NewUserService)
4. 隱式別名(建議)
註冊具體型別,並以介面調用,無需明確別名:
// 註冊具體型別
do.Provide(injector, func(i do.Injector) (*PostgreSQLDatabase, error) {
return &PostgreSQLDatabase{}, nil
})
// 直接以介面調用(隱式別名)
db := do.MustInvokeAs[Database](injector)
5. 具名服務
註冊多個相同型別的服務:
do.ProvideNamed(injector, "primary-db", func(i do.Injector) (*Database, error) {
return &Database{URL: "postgres://primary..."}, nil
})
mainDB := do.MustInvokeNamed[*Database](injector, "primary-db")
套件組織
使用 do.Package() 按模組組織服務註冊:
// infrastructure/package.go
var Package = do.Package(
do.Lazy(func(i do.Injector) (*postgres.DB, error) {
cfg := do.MustInvoke[*Config](i)
return postgres.Connect(cfg.DatabaseURL)
}),
do.Lazy(func(i do.Injector) (*redis.Client, error) {
cfg := do.MustInvoke[*Config](i)
return redis.NewClient(cfg.RedisURL), nil
}),
)
// main.go
injector := do.New(infrastructure.Package, service.Package)
完整應用程式設定
func main() {
injector := do.New(
infrastructure.Package,
repository.Package,
service.Package,
transport.Package,
)
server := do.MustInvoke[*http.Server](injector)
go server.ListenAndServe()
_ = injector.ShutdownOnSignalsWithContext(context.Background(), os.Interrupt)
}
最佳實務
- 依賴介面,而非具體型別 — 讓你在測試中無需修改正式程式碼即可替換實作
- 每個服務應只有一個職責 — 多職責的服務更難測試與替換
- 保持依賴樹淺層 — 超過 3-4 層的鏈條會使初始化順序脆弱,錯誤更難追蹤
- 在提供者函式中處理錯誤 — 默默失敗的提供者會產生損壞的服務,在後續意想不到的地方崩潰
- 使用作用域按生命週期組織服務 — 請求作用域的服務防止洩漏,全域服務防止重複初始化
如需作用域、生命週期管理、結構注入與除錯,請參閱進階用法。
如需測試模式(複製、覆寫、模擬),請參閱測試。
快速參考
註冊
| 函式 | 用途 |
|---|---|
do.Provide[T]() |
註冊 lazy 服務(預設) |
do.ProvideNamed[T]() |
註冊具名 lazy 服務 |
do.ProvideValue[T]() |
註冊預先建立的值 |
do.ProvideNamedValue[T]() |
註冊具名值 |
do.ProvideTransient[T]() |
註冊每次新實例的服務 |
do.ProvideNamedTransient[T]() |
註冊具名 transient 服務 |
do.Package() |
將服務註冊分組 |
調用
| 函式 | 用途 |
|---|---|
do.Invoke[T]() |
取得服務(含錯誤) |
do.InvokeNamed[T]() |
取得具名服務 |
do.InvokeAs[T]() |
取得第一個符合介面的服務 |
do.InvokeStruct[T]() |
使用標籤注入結構欄位 |
do.MustInvoke[T]() |
取得服務(錯誤時 panic) |
do.MustInvokeNamed[T]() |
取得具名服務(錯誤時 panic) |
do.MustInvokeAs[T]() |
依介面取得服務(錯誤時 panic) |
do.MustInvokeStruct[T]() |
注入結構(錯誤時 panic) |
交叉參考
- → 請參閱
samber/cc-skills-golang@golang-dependency-injection技能了解 DI 概念、比較與何時採用 DI 函式庫 - → 請參閱
samber/cc-skills-golang@golang-structs-interfaces技能了解介面設計模式 - → 請參閱
samber/cc-skills-golang@golang-testing技能了解一般測試模式






