使用 samber/do 在 Golang 中实现依赖注入 — 服务容器、生命周期管理、作用域、健康检查、优雅关闭和模块组织
Persona: 您是Go 建筑师, 设置依赖性注射是一项面向实际任务的技能,主要用于您将容器保存在组成根, 依赖于接口而非混凝土类型, 并将提供者错误作为首键处理。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。
实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。
执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
角色:你是一位 Go 架构师,正在搭建依赖注入系统。你将容器保留在组合根(composition root)处,依赖接口而非具体类型,并将提供者(provider)错误视为一等公民级的失败。
基于 Go 1.18+ 泛型构建的类型安全依赖注入工具包。
官方资源:
本技能介绍并不详尽。请参考该库的文档与代码示例获取更多信息。Context7 可作为可发现性平台提供辅助。
请勿使用该库的 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)
遵循“接受接口,返回结构体”原则:
// 注册服务(默认为 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})
容器仅应在组合根处被访问:
// 带错误处理地获取服务 db, err := do.Invoke[Database](injector) // MustInvoke 在出错时 panic(适用于确信服务一定存在的情形) db := do.MustInvoke[Database](injector)
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)
注册具体类型后,可直接以接口类型获取,无需显式别名:
// 注册具体类型
do.Provide(injector, func(i do.Injector) (*PostgreSQLDatabase, error) {
return &PostgreSQLDatabase{}, nil
})
// 直接以接口类型获取(隐式别名)
db := do.MustInvokeAs[Database](injector)
注册同一类型的多个服务:
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)
}
关于作用域、生命周期管理、结构体注入及调试,请参阅《高级用法》。
关于测试模式(克隆、覆盖、模拟),请参阅《测试》。
| 函数 | 用途 |
|---|---|
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]() |
通过 struct tag 向结构体字段注入依赖 |
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 技能,了解通用测试模式相关专题
热门下载
相关下载
精品课程
共0课时 | 0人学习
共0课时 | 0人学习
共0课时 | 0人学习