Persona: You are a Go architect setting up dependency injection. You keep the container at the composition root, depend on interfaces not concrete types, and treat provider errors as first-class failures.
Using samber/do for Dependency Injection in Go
Type-safe dependency injection toolkit for Go based on Go 1.18+ generics.
Official Resources:
This skill is not exhaustive. Please refer to library documentation and code examples for more information. Context7 can help as a discoverability platform.
DO NOT USE v1 OF THIS LIBRARY. INSTALL v2 INSTEAD:
CODEBLOCK0
Core Concepts
The Injector (Container)
CODEBLOCK1
Service Types
- - Lazy (default): Created when first requested
- Eager: Created immediately when the container starts
- Transient: New instance created on every request
- Value: Pre-created value, no instantiation
Provider Functions
Services MUST be registered via provider functions:
CODEBLOCK2
Basic Usage
1. Define and Register Services
Follow "Accept Interfaces, Return Structs":
CODEBLOCK3
2. Invoke Services
The container MUST only be accessed at the composition root:
CODEBLOCK4
3. Service Dependencies
CODEBLOCK5
4. Implicit Aliasing (Preferred)
Register a concrete type and invoke as an interface without explicit aliasing:
CODEBLOCK6
5. Named Services
Register multiple services of the same type:
CODEBLOCK7
Package Organization
Use do.Package() to organize service registration by module:
CODEBLOCK8
Full Application Setup
CODEBLOCK9
Best Practices
- 1. Depend on interfaces, not concrete types — lets you swap implementations in tests without touching production code
- Each service should have one job — services with multiple responsibilities are harder to test and harder to replace
- Keep dependency trees shallow — chains beyond 3-4 levels make initialization order fragile and errors harder to trace
- Handle errors in provider functions — a silently failing provider creates a broken service that crashes later in unexpected places
- Use scopes to organize services by lifecycle — request-scoped services prevent leaks, global services prevent redundant initialization
For scopes, lifecycle management, struct injection, and debugging, see Advanced Usage.
For testing patterns (cloning, overrides, mocks), see Testing.
Quick Reference
Registration
| Function | Purpose |
|---|
| INLINECODE1 | Register lazy service (default) |
| INLINECODE2 |
Register named lazy service |
|
do.ProvideValue[T]() | Register pre-created value |
|
do.ProvideNamedValue[T]() | Register named value |
|
do.ProvideTransient[T]() | Register new instance each time |
|
do.ProvideNamedTransient[T]() | Register named transient service |
|
do.Package() | Group service registrations |
Invocation
| Function | Purpose |
|---|
| INLINECODE8 | Get service (with error) |
| INLINECODE9 |
Get named service |
|
do.InvokeAs[T]() | Get first service matching interface |
|
do.InvokeStruct[T]() | Inject into struct fields using tags |
|
do.MustInvoke[T]() | Get service (panic on error) |
|
do.MustInvokeNamed[T]() | Get named service (panic on error) |
|
do.MustInvokeAs[T]() | Get service by interface (panic on error) |
|
do.MustInvokeStruct[T]() | Inject into struct (panic on error) |
Cross-References
- - → See
samber/cc-skills-golang@golang-dependency-injection skill for DI concepts, comparison, and when to adopt a DI library - → See
samber/cc-skills-golang@golang-structs-interfaces skill for interface design patterns - → See
samber/cc-skills-golang@golang-testing skill for general testing patterns
角色: 你是一位正在设置依赖注入的 Go 架构师。你将容器保持在组合根,依赖接口而非具体类型,并将提供者错误视为一等故障。
使用 samber/do 在 Go 中实现依赖注入
基于 Go 1.18+ 泛型的类型安全 Go 依赖注入工具包。
官方资源:
此技能并非详尽无遗。请参考库文档和代码示例以获取更多信息。Context7 可作为可发现性平台提供帮助。
不要使用此库的 v1 版本。请安装 v2:
bash
go get -u github.com/samber/do/v2
核心概念
注入器(容器)
go
import github.com/samber/do/v2
injector := do.New()
服务类型
- - 惰性(默认):首次请求时创建
- 即时:容器启动时立即创建
- 瞬态:每次请求时创建新实例
- 值:预先创建的值,无需实例化
提供者函数
服务必须通过提供者函数注册:
go
type Provider[T any] func(i Injector) (T, error)
基本用法
1. 定义并注册服务
遵循接受接口,返回结构体:
go
// 注册服务(默认惰性)
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.Provide(injector, do.Eager(&Config{Port: 8080}))
2. 调用服务
容器只能在组合根访问:
go
// 带错误处理的调用
db, err := do.InvokeDatabase
// MustInvoke 在出错时 panic(确信服务存在时使用)
db := do.MustInvokeDatabase
3. 服务依赖
go
func NewUserService(i do.Injector) (UserService, error) {
db := do.MustInvokeDatabase
cache := do.MustInvokeCache
return &userService{db: db, cache: cache}, nil
}
do.Provide(injector, NewUserService)
4. 隐式别名(推荐)
注册具体类型并作为接口调用,无需显式别名:
go
// 注册具体类型
do.Provide(injector, func(i do.Injector) (*PostgreSQLDatabase, error) {
return &PostgreSQLDatabase{}, nil
})
// 直接作为接口调用(隐式别名)
db := do.MustInvokeAsDatabase
5. 命名服务
注册同一类型的多个服务:
go
do.ProvideNamed(injector, primary-db, func(i do.Injector) (*Database, error) {
return &Database{URL: postgres://primary...}, nil
})
mainDB := do.MustInvokeNamed*Database
包组织
使用 do.Package() 按模块组织服务注册:
go
// infrastructure/package.go
var Package = do.Package(
do.Lazy(func(i do.Injector) (*postgres.DB, error) {
cfg := do.MustInvoke*Config
return postgres.Connect(cfg.DatabaseURL)
}),
do.Lazy(func(i do.Injector) (*redis.Client, error) {
cfg := do.MustInvoke*Config
return redis.NewClient(cfg.RedisURL), nil
}),
)
// main.go
injector := do.New(infrastructure.Package, service.Package)
完整应用设置
go
func main() {
injector := do.New(
infrastructure.Package,
repository.Package,
service.Package,
transport.Package,
)
server := do.MustInvoke*http.Server
go server.ListenAndServe()
_ = injector.ShutdownOnSignalsWithContext(context.Background(), os.Interrupt)
}
最佳实践
- 1. 依赖接口而非具体类型——让你可以在测试中切换实现而无需修改生产代码
- 每个服务应只有一个职责——职责过多的服务更难测试和替换
- 保持依赖树浅层——超过 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 技能,了解通用测试模式