golang-dependency-injection
samber/cc-skills-golang
Go 언어의 의존성 주입(DI)에 대한 종합 가이드입니다. DI의 중요성(테스트 용이성, 느슨한 결합, 관심사 분리, 라이프사이클 관리), 수동 생성자 주입, DI 라이브러리 비교(google/wire, uber-go/dig, uber-go/fx, samber/do)에 대해 다룹니다. 이 기술은 서비스 아키텍처를 설계하거나, 의존성 주입을 설정하거나, 밀접하게 결합된 코드를 리팩토링하거나, 싱글톤이나 서비스 팩토리를 관리할 때, 또는 사용자가 제어 반전(Inversion of Control), 서비스 컨테이너 등에 대해 문의할 때 활용할 수 있습니다.
...모든 것을 확장하십시오소개 golang-dependency-injection
golang-dependency-injection 특정 워크플로우에 초점을 맞춘 재사용 가능한 AI 스킬입니다. 이름: golang-dependency-injection
이 스킬은 지침, 관례 및 작업별 안내를 제공하여 에이전트가 작업을 보다 일관성 있게 수행할 수 있도록 돕습니다. 설명: "Go 언어의 의존성 주입(DI)에 대한 종합 가이드. DI의 중요성(테스트 가능성, 느슨한 결합, 관심사 분리, 라이프사이클 관리), 수동 생성자 주입, DI 라이브러리 비교(google/wire, uber-go/dig, uber-go/fx, samber/do)를 다룹니다. 서비스 아키텍처를 설계하거나, 의존성 주입을 설정하거나, 밀접하게 결합된 코드를 리팩토링하거나, 싱글톤이나 서비스 팩토리를 관리할 때, 또는 사용자가 Go 언어의 제어 반전, 서비스 컨테이너, 의존성 연결에 대해 문의할 때 이 스킬을 사용하세요. 특정 DI 라이브러리에 대해서는 → `samber/cc-skills-golang@golang-google-wire`, `samber/cc-skills-golang@golang-uber-dig`, `samber/cc-skills-golang@golang-uber-fx`, 또는 `samber/cc-skills-golang@golang-samber-do` 스킬을 참조하세요." 호환성: Claude Code 또는 이와 유사한 AI 코딩 에이전트, 그리고 Go를 사용하는 프로젝트를 위해 설계되었습니다. 홈페이지: https://github.com/samber/cc-skills-golang
실제로 이 스킬은 설정 단계가 적고 모호성이 적은 상태에서 반복적인 실행이 필요한 사용자에게 가장 적합합니다. 허용된 도구: 읽기, 편집, 쓰기, Glob, Grep, Bash(go:*), Bash(golangci-lint:*), Bash(git:*), Agent, WebFetch, mcp__context7__resolve-library-id, mcp__context7__query-docs, AskUserQuestion **페르소나:** 당신은 Go 소프트웨어 아키텍트입니다. 팀을 테스트 가능하고 느슨하게 결합된 설계 방향으로 이끕니다. 문제를 해결하는 가장 간단한 DI 접근 방식을 선택하며, 절대 과도한 설계를 하지 않습니다. - **설계 모드** (새 프로젝트, 새 서비스, 또는 기존 DI 설정에 서비스 추가): 기존 의존성 그래프와 라이프사이클 요구 사항을 평가하고, 결정 테이블에 따라 수동 주입 또는 라이브러리를 권장한 다음, 연결 코드를 생성합니다. - **리팩토링 모드** (기존의 밀접하게 결합된 코드): 최대 3개의 병렬 하위 에이전트를 사용합니다. 에이전트 1은 전역 변수와 `init()` 서비스 설정을 식별하고, 에이전트 2는 인터페이스로 전환되어야 할 구체적인 유형 의존성을 매핑하고, 에이전트 3은 서비스 로케이터 안티패턴(인수로 전달된 컨테이너)을 찾아냅니다. 이후 결과를 통합하여 마이그레이션 계획을 제안합니다.
자주 묻는 질문
golang-dependency-injection 는 어떤 도움을 주나요?
golang-dependency-injection 에이전트가 소스 문서에 설명된 집중적인 워크플로를 따르도록 도와주어, 모호성을 줄이고 실행이 의도된 작업과 일치하도록 유지합니다.
이 스킬은 언제 사용해야 하나요?
작업이 스킬 문서에 설명된 워크플로우, 도메인 또는 운영 규칙과 일치할 때, 특히 일관된 실행이 중요한 경우에 사용하십시오.
주요 제한 사항은 무엇인가요?
이 스킬은 원본 지침의 품질과 범위에 제약을 받습니다. 기본 문서가 불완전한 경우, 상담원이 추가적인 맥락 정보나 수동 검증이 필요할 수 있습니다.
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:
- 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
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 in 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 samber/cc-skills-golang@golang-structs-interfaces skill.
Best Practices Summary
- Dependencies MUST be injected via constructors — NEVER use global variables or
init()for service setup - 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 (
main()or app startup) — NEVER pass the container as a dependency - 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
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 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.
Manual Constructor Injection (No Library)
For small projects, pass dependencies through constructors. See Manual DI examples for a complete application example.
// ✓ Good — explicit dependencies, testabletype 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 wiringfunc 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, untestabletype 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 Library Comparison
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
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 (wire_gen.go) | 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 | fx.Visualize() | ExplainInjector(), web interface |
| Go version | Any | Any | Any | 1.18+ (generics) |
| Learning curve | None | Medium | High | Low |
Quick Comparison: Same App, Four Ways
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 lifecycle hooks (OnStart/OnStop) or health checks; cleanup via returned func() from providers
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 checkdo.Provide(i, NewUserStore)do.Provide(i, NewUserService)api := do.MustInvoke[*API](i)api.Run()// defer i.Shutdown() — handles all cleanup automatically
Testing with DI
DI makes testing straightforward — inject mocks instead of real implementations:
// Define a mocktype 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 injectionfunc 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
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.OverrideValue[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).
When to Adopt a DI Library
| 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 |
Common Mistakes
| 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 |
Cross-References
- → See
samber/cc-skills-golang@golang-samber-doskill for detailed samber/do usage patterns - → See
samber/cc-skills-golang@golang-structs-interfacesskill for interface design and composition - → See
samber/cc-skills-golang@golang-testingskill for testing with dependency injection - → See
samber/cc-skills-golang@golang-project-layoutskill for DI initialization placement
References
- samber/do/v2 documentation | github.com/samber/do/v2
- google/wire user guide
- uber-go/fx documentation
- uber-go/dig
모든 파일
6개 파일golang-dependency-injection 설치
스킬 파일을 다운로드하여 .claude/skills/ 디렉터리에 압축을 풀어주세요.
ZIP 다운로드저장소를 클론하고 스킬 파일을 프로젝트에 복사하세요.
git clone https://github.com/samber/cc-skills-golang/blob/main/skills/golang-dependency-injection/SKILL.md # Copy SKILL.md to your .claude/skills/ directory
복사





집
