golang-dependency-injection
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePersona: You are a Go software architect. You guide teams toward testable, loosely coupled designs — you choose the simplest DI approach that solves the problem, and you never over-engineer.
Modes:
- Design mode (new project, new service, or adding a service to an existing DI setup): assess the existing dependency graph and lifecycle needs; recommend manual injection or a library from the decision table; then generate the wiring code.
- Refactor mode (existing coupled code): use up to 3 parallel sub-agents — Agent 1 identifies global variables and service setup, Agent 2 maps concrete type dependencies that should become interfaces, Agent 3 locates service-locator anti-patterns (container passed as argument) — then consolidate findings and propose a migration plan.
init()
Community default. A company skill that explicitly supersedesskill takes precedence.samber/cc-skills-golang@golang-dependency-injection
角色定位:你是一位Go软件架构师。你指导团队打造具备可测试性、松耦合特性的设计——你会选择最简单的DI方案来解决问题,绝不过度设计。
模式:
- 设计模式(新项目、新服务,或为现有DI体系添加服务):评估现有依赖图和生命周期需求;根据决策表推荐手动注入或使用库;然后生成连接代码。
- 重构模式(现有紧耦合代码):最多使用3个并行子Agent——Agent 1识别全局变量和服务初始化,Agent 2映射应转为接口的具体类型依赖,Agent 3定位服务定位器反模式(容器作为参数传递)——然后整合结果并提出迁移方案。
init()
社区默认规则:如果有明确替代技能的公司技能,将优先使用该公司技能。samber/cc-skills-golang@golang-dependency-injection
Dependency Injection in Go
Go中的依赖注入
Dependency injection (DI) means passing dependencies to a component rather than having it create or find them. In Go, this is how you build testable, loosely coupled applications — your services declare what they need, and the caller (or container) provides it.
This skill is not exhaustive. When using a DI library (google/wire, uber-go/dig, uber-go/fx, samber/do), refer to the library's official documentation and code examples for current API signatures.
For interface-based design foundations (accept interfaces, return structs), see the skill.
samber/cc-skills-golang@golang-structs-interfaces依赖注入(DI)指的是将依赖项传递给组件,而非让组件自行创建或查找依赖项。在Go语言中,这是构建可测试、松耦合应用的方式——你的服务声明自身所需的依赖,由调用方(或容器)提供这些依赖。
本技能并非详尽无遗。使用DI库(google/wire、uber-go/dig、uber-go/fx、samber/do)时,请参考库的官方文档和代码示例以获取当前API签名。
关于基于接口的设计基础(依赖接口,返回结构体),请查看技能。
samber/cc-skills-golang@golang-structs-interfacesBest Practices Summary
最佳实践总结
- Dependencies MUST be injected via constructors — NEVER use global variables or for service setup
init() - Small projects (< 10 services) SHOULD use manual constructor injection — no library needed
- Interfaces MUST be defined where consumed, not where implemented — accept interfaces, return structs
- NEVER use global registries or package-level service locators
- The DI container MUST only exist at the composition root (or app startup) — NEVER pass the container as a dependency
main() - Prefer lazy initialization — only create services when first requested
- Use singletons for stateful services (DB connections, caches) and transients for stateless ones
- Mock at the interface boundary — DI makes this trivial
- Keep the dependency graph shallow — deep chains signal design problems
- Choose the right DI library for your project size and team — see the decision table below
- 依赖项必须通过构造函数注入——绝不要使用全局变量或进行服务初始化
init() - 小型项目(<10个服务)应使用手动构造函数注入——无需使用库
- 接口必须在消费端定义,而非实现端——依赖接口,返回结构体
- 绝不要使用全局注册中心或包级服务定位器
- DI容器只能存在于组合根(或应用启动处)——绝不要将容器作为依赖项传递
main() - 优先使用延迟初始化——仅在首次请求时创建服务
- 有状态服务使用单例(数据库连接、缓存),无状态服务使用瞬态实例
- 在接口边界处进行Mock——DI让这一操作变得简单
- 保持依赖图层级较浅——深层依赖链意味着设计存在问题
- 为项目规模和团队选择合适的DI库——请查看下方的决策表
Why Dependency Injection?
为什么需要依赖注入?
| Problem without DI | How DI solves it |
|---|---|
| Functions create their own dependencies | Dependencies are injected — swap implementations freely |
| Testing requires real databases, APIs | Pass mock implementations in tests |
| Changing one component breaks others | Loose coupling via interfaces — components don't know each other's internals |
| Services initialized everywhere | Centralized container manages lifecycle (singleton, factory, lazy) |
| All services loaded at startup | Lazy loading — services created only when first requested |
Global state and | Explicit wiring at startup — predictable, debuggable |
DI shines in applications with many interconnected services — HTTP servers, microservices, CLI tools with plugins. For a small script with 2-3 functions, manual wiring is fine. Don't over-engineer.
| 无DI时的问题 | DI的解决方案 |
|---|---|
| 函数自行创建依赖项 | 依赖项被注入——可自由替换实现 |
| 测试需要真实数据库、API | 在测试中传递Mock实现 |
| 修改一个组件会破坏其他组件 | 通过接口实现松耦合——组件无需了解彼此的内部实现 |
| 服务在各处初始化 | 集中式容器管理生命周期(单例、工厂、延迟) |
| 所有服务在启动时加载 | 延迟加载——仅在首次请求时创建服务 |
全局状态和 | 在启动时显式连接——可预测、易于调试 |
DI在拥有大量互联服务的应用中表现出色——HTTP服务器、微服务、带插件的CLI工具。对于只有2-3个函数的小型脚本,手动连接即可。不要过度设计。
Manual Constructor Injection (No Library)
手动构造函数注入(无需库)
For small projects, pass dependencies through constructors. See Manual DI examples for a complete application example.
go
// ✓ Good — explicit dependencies, testable
type UserService struct {
db UserStore
mailer Mailer
logger *slog.Logger
}
func NewUserService(db UserStore, mailer Mailer, logger *slog.Logger) *UserService {
return &UserService{db: db, mailer: mailer, logger: logger}
}
// main.go — manual wiring
func main() {
logger := slog.Default()
db := postgres.NewUserStore(connStr)
mailer := smtp.NewMailer(smtpAddr)
userSvc := NewUserService(db, mailer, logger)
orderSvc := NewOrderService(db, logger)
api := NewAPI(userSvc, orderSvc, logger)
api.ListenAndServe(":8080")
}go
// ✗ Bad — hardcoded dependencies, untestable
type UserService struct {
db *sql.DB
}
func NewUserService() *UserService {
db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL")) // hidden dependency
return &UserService{db: db}
}Manual DI breaks down when:
- You have 15+ services with cross-dependencies
- You need lifecycle management (health checks, graceful shutdown)
- You want lazy initialization or scoped containers
- Wiring order becomes fragile and hard to maintain
对于小型项目,通过构造函数传递依赖项。完整的应用示例请查看手动DI示例。
go
// ✓ 良好实践 —— 依赖项明确,可测试
type UserService struct {
db UserStore
mailer Mailer
logger *slog.Logger
}
func NewUserService(db UserStore, mailer Mailer, logger *slog.Logger) *UserService {
return &UserService{db: db, mailer: mailer, logger: logger}
}
// main.go —— 手动连接
func main() {
logger := slog.Default()
db := postgres.NewUserStore(connStr)
mailer := smtp.NewMailer(smtpAddr)
userSvc := NewUserService(db, mailer, logger)
orderSvc := NewOrderService(db, logger)
api := NewAPI(userSvc, orderSvc, logger)
api.ListenAndServe(":8080")
}go
// ✗ 不良实践 —— 依赖项硬编码,不可测试
type UserService struct {
db *sql.DB
}
func NewUserService() *UserService {
db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL")) // 隐藏的依赖项
return &UserService{db: db}
}当出现以下情况时,手动DI不再适用:
- 拥有15个以上存在交叉依赖的服务
- 需要生命周期管理(健康检查、优雅停机)
- 想要延迟初始化或作用域容器
- 连接顺序变得脆弱且难以维护
DI Library Comparison
DI库对比
Go has three main approaches to DI libraries:
- google/wire examples — Compile-time code generation
- uber-go/dig + fx examples — Reflection-based framework
- samber/do examples — Generics-based, no code generation
Go的DI库主要有三种实现方式:
- google/wire示例 —— 编译时代码生成
- uber-go/dig + fx示例 —— 基于反射的框架
- samber/do示例 —— 基于泛型,无需代码生成
Decision Table
决策表
| Criteria | Manual | google/wire | uber-go/dig + fx | samber/do |
|---|---|---|---|---|
| Project size | Small (< 10 services) | Medium-Large | Large | Any size |
| Type safety | Compile-time | Compile-time (codegen) | Runtime (reflection) | Compile-time (generics) |
| Code generation | None | Required ( | None | None |
| Reflection | None | None | Yes | None |
| API style | N/A | Provider sets + build tags | Struct tags + decorators | Simple, generic functions |
| Lazy loading | Manual | N/A (all eager) | Built-in (fx) | Built-in |
| Singletons | Manual | Built-in | Built-in | Built-in |
| Transient/factory | Manual | Manual | Built-in | Built-in |
| Scopes/modules | Manual | Provider sets | Module system (fx) | Built-in (hierarchical) |
| Health checks | Manual | Manual | Manual | Built-in interface |
| Graceful shutdown | Manual | Manual | Built-in (fx) | Built-in interface |
| Container cloning | N/A | N/A | N/A | Built-in |
| Debugging | Print statements | Compile errors | | |
| Go version | Any | Any | Any | 1.18+ (generics) |
| Learning curve | None | Medium | High | Low |
| 评估标准 | 手动注入 | google/wire | uber-go/dig + fx | samber/do |
|---|---|---|---|---|
| 项目规模 | 小型(<10个服务) | 中大型 | 大型 | 任意规模 |
| 类型安全 | 编译时 | 编译时(代码生成) | 运行时(反射) | 编译时(泛型) |
| 代码生成 | 无 | 需要( | 无 | 无 |
| 反射 | 无 | 无 | 是 | 无 |
| API风格 | N/A | 提供者集合 + 构建标签 | 结构体标签 + 装饰器 | 简单的泛型函数 |
| 延迟加载 | 手动实现 | 不支持(全部立即加载) | 内置支持(fx) | 内置支持 |
| 单例 | 手动实现 | 内置支持 | 内置支持 | 内置支持 |
| 瞬态/工厂 | 手动实现 | 手动实现 | 内置支持 | 内置支持 |
| 作用域/模块 | 手动实现 | 提供者集合 | 模块系统(fx) | 内置支持(分层) |
| 健康检查 | 手动实现 | 手动实现 | 手动实现 | 内置接口 |
| 优雅停机 | 手动实现 | 手动实现 | 内置支持(fx) | 内置接口 |
| 容器克隆 | N/A | N/A | N/A | 内置支持 |
| 调试 | 打印语句 | 编译错误 | | |
| Go版本要求 | 任意 | 任意 | 任意 | 1.18+(泛型) |
| 学习曲线 | 无 | 中等 | 高 | 低 |
Quick Comparison: Same App, Four Ways
快速对比:同一应用的四种实现方式
The dependency graph:
Config -> Database -> UserStore -> UserService -> APIManual:
go
cfg := NewConfig()
db := NewDatabase(cfg)
store := NewUserStore(db)
svc := NewUserService(store)
api := NewAPI(svc)
api.Run()
// No automatic shutdown, health checks, or lazy loadinggoogle/wire:
go
// wire.go — then run: wire ./...
func InitializeAPI() (*API, error) {
wire.Build(NewConfig, NewDatabase, NewUserStore, NewUserService, NewAPI)
return nil, nil
}
// No shutdown or health check supportuber-go/fx:
go
app := fx.New(
fx.Provide(NewConfig, NewDatabase, NewUserStore, NewUserService),
fx.Invoke(func(api *API) { api.Run() }),
)
app.Run() // manages lifecycle, but reflection-basedsamber/do:
go
i := do.New()
do.Provide(i, NewConfig)
do.Provide(i, NewDatabase) // auto shutdown + health check
do.Provide(i, NewUserStore)
do.Provide(i, NewUserService)
api := do.MustInvoke[*API](i)
api.Run()
// defer i.Shutdown() — handles all cleanup automatically依赖图:
Config -> Database -> UserStore -> UserService -> API手动注入:
go
cfg := NewConfig()
db := NewDatabase(cfg)
store := NewUserStore(db)
svc := NewUserService(store)
api := NewAPI(svc)
api.Run()
// 无自动停机、健康检查或延迟加载google/wire:
go
// wire.go —— 然后运行:wire ./...
func InitializeAPI() (*API, error) {
wire.Build(NewConfig, NewDatabase, NewUserStore, NewUserService, NewAPI)
return nil, nil
}
// 无停机或健康检查支持uber-go/fx:
go
app := fx.New(
fx.Provide(NewConfig, NewDatabase, NewUserStore, NewUserService),
fx.Invoke(func(api *API) { api.Run() }),
)
app.Run() // 管理生命周期,但基于反射samber/do:
go
i := do.New()
do.Provide(i, NewConfig)
do.Provide(i, NewDatabase) // 自动停机 + 健康检查
do.Provide(i, NewUserStore)
do.Provide(i, NewUserService)
api := do.MustInvoke[*API](i)
api.Run()
// defer i.Shutdown() —— 自动处理所有清理工作Testing with DI
结合DI进行测试
DI makes testing straightforward — inject mocks instead of real implementations:
go
// Define a mock
type MockUserStore struct {
users map[string]*User
}
func (m *MockUserStore) FindByID(ctx context.Context, id string) (*User, error) {
u, ok := m.users[id]
if !ok {
return nil, ErrNotFound
}
return u, nil
}
// Test with manual injection
func TestUserService_GetUser(t *testing.T) {
mock := &MockUserStore{
users: map[string]*User{"1": {ID: "1", Name: "Alice"}},
}
svc := NewUserService(mock, nil, slog.Default())
user, err := svc.GetUser(context.Background(), "1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got %q, want %q", user.Name, "Alice")
}
}DI让测试变得简单——注入Mock实现而非真实实现:
go
// 定义Mock
type MockUserStore struct {
users map[string]*User
}
func (m *MockUserStore) FindByID(ctx context.Context, id string) (*User, error) {
u, ok := m.users[id]
if !ok {
return nil, ErrNotFound
}
return u, nil
}
// 手动注入进行测试
func TestUserService_GetUser(t *testing.T) {
mock := &MockUserStore{
users: map[string]*User{"1": {ID: "1", Name: "Alice"}},
}
svc := NewUserService(mock, nil, slog.Default())
user, err := svc.GetUser(context.Background(), "1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got %q, want %q", user.Name, "Alice")
}
}Testing with samber/do — Clone and Override
结合samber/do进行测试——克隆与覆盖
Container cloning creates an isolated copy where you override only the services you need to mock:
go
func TestUserService_WithDo(t *testing.T) {
// Create a test injector with mock implementation
testInjector := do.New()
// Provide the mock UserStore interface
do.Override[UserStore](testInjector, &MockUserStore{
users: map[string]*User{"1": {ID: "1", Name: "Alice"}},
})
// Provide other real services as needed
do.Provide[*slog.Logger](testInjector, func(i *do.Injector) (*slog.Logger, error) {
return slog.Default(), nil
})
svc := do.MustInvoke[*UserService](testInjector)
user, err := svc.GetUser(context.Background(), "1")
// ... assertions
}This is particularly useful for integration tests where you want most services to be real but need to mock a specific boundary (database, external API, mailer).
容器克隆会创建一个独立的副本,你可以在其中仅覆盖需要Mock的服务:
go
func TestUserService_WithDo(t *testing.T) {
// 创建带有Mock实现的测试注入器
testInjector := do.New()
// 提供Mock UserStore接口
do.Override[UserStore](testInjector, &MockUserStore{
users: map[string]*User{"1": {ID: "1", Name: "Alice"}},
})
// 根据需要提供其他真实服务
do.Provide[*slog.Logger](testInjector, func(i *do.Injector) (*slog.Logger, error) {
return slog.Default(), nil
})
svc := do.MustInvoke[*UserService](testInjector)
user, err := svc.GetUser(context.Background(), "1")
// ... 断言逻辑
}这在集成测试中尤其有用,此时你希望大多数服务为真实实现,但需要Mock特定边界(数据库、外部API、邮件服务)。
When to Adopt a DI Library
何时采用DI库
| Signal | Action |
|---|---|
| < 10 services, simple dependencies | Stay with manual constructor injection |
| 10-20 services, some cross-cutting concerns | Consider a DI library |
| 20+ services, lifecycle management needed | Strongly recommended |
| Need health checks, graceful shutdown | Use a library with built-in lifecycle support |
| Team unfamiliar with DI concepts | Start manual, migrate incrementally |
| 信号 | 行动 |
|---|---|
| <10个服务,依赖关系简单 | 继续使用手动构造函数注入 |
| 10-20个服务,存在一些横切关注点 | 考虑使用DI库 |
| 20+个服务,需要生命周期管理 | 强烈推荐使用DI库 |
| 需要健康检查、优雅停机 | 使用内置生命周期支持的库 |
| 团队不熟悉DI概念 | 从手动注入开始,逐步迁移 |
Common Mistakes
常见错误
| Mistake | Fix |
|---|---|
| Global variables as dependencies | Pass through constructors or DI container |
| Explicit initialization in |
| Depending on concrete types | Accept interfaces at consumption boundaries |
| Passing the container everywhere (service locator) | Inject specific dependencies, not the container |
| Deep dependency chains (A->B->C->D->E) | Flatten — most services should depend on repositories and config directly |
| Creating a new container per request | One container per application; use scopes for request-level isolation |
| 错误 | 修复方案 |
|---|---|
| 将全局变量作为依赖项 | 通过构造函数或DI容器传递 |
使用 | 在 |
| 依赖具体类型 | 在消费边界处依赖接口 |
| 到处传递容器(服务定位器) | 注入特定依赖项,而非容器本身 |
| 深层依赖链(A->B->C->D->E) | 扁平化——大多数服务应直接依赖仓库和配置 |
| 为每个请求创建新容器 | 每个应用使用一个容器;使用作用域实现请求级隔离 |
Cross-References
交叉引用
- → See skill for detailed samber/do usage patterns
samber/cc-skills-golang@golang-samber-do - → See skill for interface design and composition
samber/cc-skills-golang@golang-structs-interfaces - → See skill for testing with dependency injection
samber/cc-skills-golang@golang-testing - → See skill for DI initialization placement
samber/cc-skills-golang@golang-project-layout
- → 查看技能,获取samber/do的详细使用模式
samber/cc-skills-golang@golang-samber-do - → 查看技能,了解接口设计与组合
samber/cc-skills-golang@golang-structs-interfaces - → 查看技能,了解结合依赖注入的测试方法
samber/cc-skills-golang@golang-testing - → 查看技能,了解DI初始化的放置位置
samber/cc-skills-golang@golang-project-layout