golang-dependency-injection by samber/cc-skills-golang
npx skills add https://github.com/samber/cc-skills-golang --skill golang-dependency-injection角色: 你是一位 Go 软件架构师。你引导团队走向可测试、松耦合的设计——你选择能解决问题的最简单的依赖注入方法,并且从不进行过度设计。
模式:
init() 服务设置,代理 2 映射应转换为接口的具体类型依赖,代理 3 定位服务定位器反模式(容器作为参数传递)——然后整合发现并提出迁移计划。社区默认值。 明确取代
samber/cc-skills-golang@golang-dependency-injection技能的团队技能具有优先权。
依赖注入(DI)意味着将依赖项传递给组件,而不是让组件自己创建或查找它们。在 Go 中,这是构建可测试、松耦合应用程序的方式——你的服务声明它们需要什么,调用者(或容器)则提供它。
本技能并非详尽无遗。在使用 DI 库(google/wire, uber-go/dig, uber-go/fx, samber/do)时,请参考该库的官方文档和代码示例以获取最新的 API 签名。
关于基于接口的设计基础(接受接口,返回结构体),请参阅 samber/cc-skills-golang@golang-structs-interfaces 技能。
init() 进行服务设置广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
main() 或应用启动时)——绝不将容器作为依赖项传递| 没有 DI 的问题 | DI 如何解决它 |
|---|---|
| 函数创建自己的依赖项 | 依赖项被注入——可以自由交换实现 |
| 测试需要真实的数据库、API | 在测试中传递模拟实现 |
| 更改一个组件会破坏其他组件 | 通过接口实现松耦合——组件不了解彼此的内部实现 |
| 服务随处初始化 | 集中式容器管理生命周期(单例、工厂、延迟) |
| 所有服务在启动时加载 | 延迟加载——仅在首次请求时创建服务 |
全局状态和 init() 函数 | 在启动时显式装配——可预测、可调试 |
DI 在具有许多互连服务的应用程序中大放异彩——HTTP 服务器、微服务、带有插件的 CLI 工具。对于只有 2-3 个函数的小脚本,手动装配即可。不要过度设计。
对于小型项目,通过构造函数传递依赖项。查看 手动 DI 示例 获取完整的应用程序示例。
// ✓ 良好——显式依赖,可测试
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")
}
// ✗ 糟糕——硬编码依赖,不可测试
type UserService struct {
db *sql.DB
}
func NewUserService() *UserService {
db, _ := sql.Open("postgres", os.Getenv("DATABASE_URL")) // 隐藏的依赖
return &UserService{db: db}
}
手动 DI 在以下情况下会失效:
Go 有三种主要的 DI 库方法:
| 标准 | 手动 | google/wire | uber-go/dig + fx | samber/do |
|---|---|---|---|---|
| 项目规模 | 小型(< 10 个服务) | 中大型 | 大型 | 任意规模 |
| 类型安全 | 编译时 | 编译时(代码生成) | 运行时(反射) | 编译时(泛型) |
| 代码生成 | 无 | 必需(wire_gen.go) | 无 | 无 |
| 反射 | 无 | 无 | 是 | 无 |
| API 风格 | 不适用 | 提供者集合 + 构建标签 | 结构体标签 + 装饰器 | 简单,泛型函数 |
| 延迟加载 | 手动 | 不适用(全部急切) | 内置(fx) | 内置 |
| 单例 | 手动 | 内置 | 内置 | 内置 |
| 瞬时/工厂 | 手动 | 手动 | 内置 | 内置 |
| 作用域/模块 | 手动 | 提供者集合 | 模块系统(fx) | 内置(分层) |
| 健康检查 | 手动 | 手动 | 手动 | 内置接口 |
| 优雅关闭 | 手动 | 手动 | 内置(fx) | 内置接口 |
| 容器克隆 | 不适用 | 不适用 | 不适用 | 内置 |
| 调试 | 打印语句 | 编译错误 | fx.Visualize() | ExplainInjector(), 网页界面 |
| Go 版本 | 任意 | 任意 | 任意 | 1.18+(泛型) |
| 学习曲线 | 无 | 中等 | 高 | 低 |
依赖图:Config -> Database -> UserStore -> UserService -> API
手动:
cfg := NewConfig()
db := NewDatabase(cfg)
store := NewUserStore(db)
svc := NewUserService(store)
api := NewAPI(svc)
api.Run()
// 没有自动关闭、健康检查或延迟加载
google/wire:
// wire.go —— 然后运行:wire ./...
func InitializeAPI() (*API, error) {
wire.Build(NewConfig, NewDatabase, NewUserStore, NewUserService, NewAPI)
return nil, nil
}
// 没有关闭或健康检查支持
uber-go/fx:
app := fx.New(
fx.Provide(NewConfig, NewDatabase, NewUserStore, NewUserService),
fx.Invoke(func(api *API) { api.Run() }),
)
app.Run() // 管理生命周期,但基于反射
samber/do:
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() —— 自动处理所有清理工作
DI 使测试变得简单——注入模拟实现而不是真实实现:
// 定义一个模拟
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")
}
}
容器克隆创建一个隔离的副本,你可以在其中仅覆盖需要模拟的服务:
func TestUserService_WithDo(t *testing.T) {
// 创建一个带有模拟实现的测试注入器
testInjector := do.New()
// 提供模拟的 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")
// ... 断言
}
这对于集成测试特别有用,你希望大多数服务是真实的,但需要模拟特定的边界(数据库、外部 API、邮件发送器)。
| 信号 | 行动 |
|---|---|
| < 10 个服务,简单依赖 | 坚持使用手动构造函数注入 |
| 10-20 个服务,存在一些横切关注点 | 考虑使用 DI 库 |
| 20+ 个服务,需要生命周期管理 | 强烈推荐 |
| 需要健康检查、优雅关闭 | 使用具有内置生命周期支持的库 |
| 团队不熟悉 DI 概念 | 从手动开始,逐步迁移 |
| 错误 | 修复方法 |
|---|---|
| 将全局变量作为依赖项 | 通过构造函数或 DI 容器传递 |
使用 init() 进行服务设置 | 在 main() 或容器中显式初始化 |
| 依赖具体类型 | 在使用边界接受接口 |
| 到处传递容器(服务定位器) | 注入特定的依赖项,而不是容器 |
| 深层依赖链(A->B->C->D->E) | 扁平化——大多数服务应直接依赖于存储库和配置 |
| 为每个请求创建新容器 | 每个应用程序一个容器;使用作用域进行请求级隔离 |
samber/cc-skills-golang@golang-samber-do 技能,了解详细的 samber/do 使用模式samber/cc-skills-golang@golang-structs-interfaces 技能,了解接口设计和组合samber/cc-skills-golang@golang-testing 技能,了解使用依赖注入进行测试samber/cc-skills-golang@golang-project-layout 技能,了解 DI 初始化放置位置每周安装量
129
代码库
GitHub 星标数
276
首次出现
4 天前
安全审计
安装于
opencode109
gemini-cli106
codex106
cursor106
amp105
github-copilot105
Persona: 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:
init() 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.Community default. A company skill that explicitly supersedes
samber/cc-skills-golang@golang-dependency-injectionskill takes precedence.
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 samber/cc-skills-golang@golang-structs-interfaces skill.
init() for service setupmain() or app startup) — NEVER pass the container as a dependency| 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 init() functions | 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.
For small projects, pass dependencies through constructors. See Manual DI examples for a complete application example.
// ✓ 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")
}
// ✗ 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:
Go has three main approaches to DI libraries:
| 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 (wire_gen.go) | None | None |
| Reflection | None | None | Yes | None |
| API style |
The dependency graph: Config -> Database -> UserStore -> UserService -> API
Manual :
cfg := NewConfig()
db := NewDatabase(cfg)
store := NewUserStore(db)
svc := NewUserService(store)
api := NewAPI(svc)
api.Run()
// No automatic shutdown, health checks, or lazy loading
google/wire :
// wire.go — then run: wire ./...
func InitializeAPI() (*API, error) {
wire.Build(NewConfig, NewDatabase, NewUserStore, NewUserService, NewAPI)
return nil, nil
}
// No shutdown or health check support
uber-go/fx :
app := fx.New(
fx.Provide(NewConfig, NewDatabase, NewUserStore, NewUserService),
fx.Invoke(func(api *API) { api.Run() }),
)
app.Run() // manages lifecycle, but reflection-based
samber/do :
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
DI makes testing straightforward — inject mocks instead of real implementations:
// 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")
}
}
Container cloning creates an isolated copy where you override only the services you need to mock:
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).
| 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 |
| Mistake | Fix |
|---|---|
| Global variables as dependencies | Pass through constructors or DI container |
init() for service setup | Explicit initialization in main() or container |
| 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 |
samber/cc-skills-golang@golang-samber-do skill for detailed samber/do usage patternssamber/cc-skills-golang@golang-structs-interfaces skill for interface design and compositionsamber/cc-skills-golang@golang-testing skill for testing with dependency injectionsamber/cc-skills-golang@golang-project-layout skill for DI initialization placementWeekly Installs
129
Repository
GitHub Stars
276
First Seen
4 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode109
gemini-cli106
codex106
cursor106
amp105
github-copilot105
Kotlin Ktor 服务器模式指南:构建健壮 HTTP API 的架构与最佳实践
1,000 周安装
Google 联系人导出到 Sheets 自动化脚本 | 批量同步联系人数据
7,100 周安装
GDPR 数据处理指南 - 合规实施、同意管理与隐私控制实战教程
7,000 周安装
面向对象组件文档自动生成工具 - 遵循C4/Arc42/IEEE标准
7,000 周安装
GitHub Copilot Dataverse MCP 配置指南:自动发现与手动设置环境URL
7,000 周安装
AI自动更新面向对象组件文档 | 遵循C4/Arc42/IEEE标准 | GitHub Copilot工具
7,000 周安装
Web表单创建全攻略 - HTML/CSS/JS表单设计、PHP/Python服务器处理与验证
7,000 周安装
| 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 | fx.Visualize() | ExplainInjector(), web interface |
| Go version | Any | Any | Any | 1.18+ (generics) |
| Learning curve | None | Medium | High | Low |