golang-samber-do

golang-samber-do

热门

使用 samber/do 在 Golang 中进行依赖注入——服务容器、生命周期管理、作用域、健康检查、优雅关闭和模块组织。适用于使用或采用 samber/do 时,代码库导入 github.com/samber/do 或 github.com/samber/do/v2 时,或者将手动构造函数注入重构为 DI 容器时。

2261Star
150Fork
更新于 2026/6/6
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)
}

最佳实践

  1. 依赖接口而非具体类型——允许在测试中替换实现而不影响生产代码
  2. 每个服务应只有一个职责——多职责的服务更难测试和替换
  3. 保持依赖树浅层——超过 3-4 层的链会使初始化顺序脆弱且错误更难追踪
  4. 在提供者函数中处理错误——静默失败的提供者会创建损坏的服务,在后续位置意外崩溃
  5. 使用作用域按生命周期组织服务——请求作用域的服务防止泄漏,全局服务防止重复初始化

有关作用域、生命周期管理、结构体注入和调试,请参见 高级用法

有关测试模式(克隆、覆盖、模拟),请参见 测试

快速参考

注册

函数 用途
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 技能了解通用测试模式