mcpField
G
golang-samber-do

golang-samber-do

github.com/samber开发3.7k38.8k 次安装5 天前更新
代码仓库

关于 golang-samber-do

面向 Go 开发者的 samber/do 使用技能,介绍基于泛型的依赖注入工具。内容侧重组合根容器管理、提供器错误处理、作用域和模块化注册。

主要功能

  1. 01基于泛型的依赖注入
  2. 02惰性、即时、瞬态和值服务
  3. 03按名称及接口解析服务
  4. 04通过 Package 组织注册
  5. 05作用域与生命周期指导

适用场景

  1. 01为 Go 应用配置依赖注入
  2. 02按应用模块注册服务
  3. 03从提供器解析接口实现

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-do

安装后在 Claude Code 中输入/golang-samber-do确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
advanced.md
testing.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)
}

最佳实践

  1. 依赖接口,而非具体类型 — 这样无需修改生产代码,就能在测试中替换实现
  2. 每个服务只负责一项工作 — 有多项职责的服务更难测试,也更难替换
  3. 保持依赖树精简 — 超过 3-4 层的依赖链会使初始化顺序变得脆弱,也更难追踪错误
  4. 在 provider 函数中处理错误 — 静默失败的 provider 会创建一个损坏的服务,之后在意料之外的位置崩溃
  5. 使用作用域按生命周期组织服务 — 请求作用域的服务可防止资源泄漏,全局服务可避免重复初始化
  6. 在 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