关于 golang-samber-do
面向 Go 开发者的 samber/do 使用技能,介绍基于泛型的依赖注入工具。内容侧重组合根容器管理、提供器错误处理、作用域和模块化注册。
主要功能
- 01基于泛型的依赖注入
- 02惰性、即时、瞬态和值服务
- 03按名称及接口解析服务
- 04通过 Package 组织注册
- 05作用域与生命周期指导
适用场景
- 01为 Go 应用配置依赖注入
- 02按应用模块注册服务
- 03从提供器解析接口实现
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ samber/ cc-skills-golang --skill golang-samber-do安装后在 Claude Code 中输入/golang-samber-do确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/advanced.mdtesting.md详细介绍
Persona: 你是一位负责设置依赖注入的 Go 架构师。你将容器保留在组合根中,依赖接口而非具体类型,并将 provider 错误视为一等故障。
使用 samber/do 在 Go 中进行依赖注入
基于 Go 1.18+ 泛型的 Go 类型安全依赖注入工具包。
官方资源:
本技能并未涵盖所有内容——更多信息请参阅库文档和代码示例:
- 对于 Go 包文档、符号、版本、导入者和已知漏洞,→ 请参阅
samber/cc-skills-golang@golang-pkg-go-dev技能(godig),查询 Go 包信息时优先于 Context7。 - 若要在自己的代码中导航此库的用法(定义、调用点、诊断信息),→ 请参阅
samber/cc-skills-golang@golang-gopls技能(gopls)。 - 对于未在 pkg.go.dev 上编入索引的文档,Context7 仍可作为后备选择。
安装 v2——v1 已被取代,且缺少下文所述的基于泛型的容器、作用域和生命周期钩子,因此 v1 时代的指导会误导本技能中的所有 API:
go get -u github.com/samber/do/v2
核心概念
注入器(容器)
import "github.com/samber/do/v2"
injector := do.New()
服务类型
- 延迟创建(默认):首次请求时创建
- 急切创建:容器启动时立即创建
- 瞬态:每次请求都会创建新实例
- 值:预先创建的值,无需实例化
Provider 函数
服务必须通过 provider 函数注册:
type Provider[T any] func(i Injector) (T, error)
基本用法
1. 定义并注册服务
遵循“接受接口,返回结构体”:
// Register a service (lazy by default)
do.Provide(injector, func(i do.Injector) (Database, error) {
return &PostgreSQLDatabase{connString: "postgres://..."}, nil
})
// Register a pre-created value
do.ProvideValue(injector, &Config{Port: 8080})
// Register a transient service (new instance each time)
do.ProvideTransient(injector, func(i do.Injector) (*Logger, error) {
return &Logger{}, nil
})
// Register an eager service (created immediately at startup)
do.ProvideValue(injector, &Config{Port: 8080})
2. 调用服务
容器只能在组合根中访问:
// Invoke with error handling — reserve for call sites outside the DI graph
// (e.g. an HTTP handler that must degrade gracefully instead of crashing)
db, err := do.Invoke[Database](injector)
// MustInvoke panics on error — preferred in providers, recovered by do.Invoke on the parent call
db := do.MustInvoke[Database](injector)
在 provider 函数中,始终使用 do.MustInvoke(或 MustInvokeAs/MustInvokeNamed/MustInvokeStruct),而不是返回错误的变体:
- Provider 已经返回
(T, error),因此使用do.Invoke传播依赖故障,每次调用都要额外编写if err != nil { return nil, err }。 do.MustInvoke会改为触发 panic,但 samber/do 会在外层的Invoke调用中正确捕获并恢复该 panic,再将其转换回常规错误——此恢复由库内部完成,而非调用方代码,因此在 provider 中使用MustInvoke是安全的。- 故障仍会在组合根处作为错误显现,只是不必在每个 provider 中手动编写样板代码。
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. 隐式别名(推荐)
注册具体类型,并在不显式设置别名的情况下以接口调用:
// Register concrete type
do.Provide(injector, func(i do.Injector) (*PostgreSQLDatabase, error) {
return &PostgreSQLDatabase{}, nil
})
// Invoke directly as interface (implicit aliasing)
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 层的依赖链会使初始化顺序变得脆弱,也更难追踪错误
- 在 provider 函数中处理错误 — 静默失败的 provider 会创建一个损坏的服务,之后在意料之外的位置崩溃
- 使用作用域按生命周期组织服务 — 请求作用域的服务可防止资源泄漏,全局服务可避免重复初始化
- 在 provider 函数中使用
do.MustInvoke*,而不是do.Invoke*— samber/do 会在外层Invoke调用中正确捕获并恢复 panic,将其转回返回的错误,因此可以安全地在 provider 中使用,并且无需样板代码即可获得相同的错误传递效果
有关作用域、生命周期管理、结构体注入和调试,请参阅 高级用法。
有关测试模式(克隆、覆盖、模拟),请参阅 测试。
快速参考
注册
| 函数 | 用途 |
|---|---|
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) |
交叉引用
- → DI 概念、对比以及何时采用 DI 库,请参阅
samber/cc-skills-golang@golang-dependency-injection技能 - → 接口设计模式,请参阅
samber/cc-skills-golang@golang-structs-interfaces技能 - → 通用测试模式,请参阅
samber/cc-skills-golang@golang-testing技能
评论
支持 Markdown