SKILL.md
只读
名称
golang-samber-do
描述
使用 samber/do 在 Golang 中进行依赖注入——服务容器、生命周期管理、作用域、健康检查、优雅关闭和模块组织。适用于使用或采用 samber/do 时,代码库导入 github.com/samber/do 或 github.com/samber/do/v2 时,或者将手动构造函数注入重构为 DI 容器时。
角色: 你是一位正在设置依赖注入的 Go 架构师。你将容器保持在组合根,依赖接口而非具体类型,并将提供者错误视为一等失败。
使用 samber/do 进行 Go 依赖注入
基于 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()
服务类型
- 惰性(默认):首次请求时创建
- 即时:容器启动时立即创建
- 瞬态:每次请求创建新实例
- 值:预先创建的值,无需实例化
提供者函数
服务必须通过提供者函数注册:
type Provider[T any] func(i Injector) (T, error)
基本用法
1. 定义和注册服务
遵循“接受接口,返回结构体”:
// 注册服务(默认惰性)
do.Provide(injector, func(i do.Injector) (Database, error) {
return &PostgreSQLDatabase{connString: "postgres://..."}, nil
})
// 注册预先创建的值
do.ProvideValue(injector, &Config{Port: 8080})
// 注册瞬态服务(每次新实例)
do.ProvideTransient(injector, func(i do.Injector) (*Logger, error) {
return &Logger{}, nil
})
// 注册即时服务(启动时立即创建)
do.ProvideValue(injector, &Config{Port: 8080})
2. 调用服务
容器只能在组合根访问:
// 带错误处理的调用
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]() |
注册惰性服务(默认) |
do.ProvideNamed[T]() |
注册命名惰性服务 |
do.ProvideValue[T]() |
注册预先创建的值 |
do.ProvideNamedValue[T]() |
注册命名值 |
do.ProvideTransient[T]() |
注册每次新实例的服务 |
do.ProvideNamedTransient[T]() |
注册命名瞬态服务 |
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技能了解通用测试模式






