옵션
집 Skill 선적 서류 비치 golang-dependency-injection

golang-dependency-injection

samber/cc-skills-golang samber/cc-skills-golang

Go 언어의 의존성 주입(DI)에 대한 종합 가이드입니다. DI의 중요성(테스트 용이성, 느슨한 결합, 관심사 분리, 라이프사이클 관리), 수동 생성자 주입, DI 라이브러리 비교(google/wire, uber-go/dig, uber-go/fx, samber/do)에 대해 다룹니다. 이 기술은 서비스 아키텍처를 설계하거나, 의존성 주입을 설정하거나, 밀접하게 결합된 코드를 리팩토링하거나, 싱글톤이나 서비스 팩토리를 관리할 때, 또는 사용자가 제어 반전(Inversion of Control), 서비스 컨테이너 등에 대해 문의할 때 활용할 수 있습니다.

...모든 것을 확장하십시오
42
업데이트 된 시간 2026년 6월 29일

소개 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 에이전트가 소스 문서에 설명된 집중적인 워크플로를 따르도록 도와주어, 모호성을 줄이고 실행이 의도된 작업과 일치하도록 유지합니다.

이 스킬은 언제 사용해야 하나요?

작업이 스킬 문서에 설명된 워크플로우, 도메인 또는 운영 규칙과 일치할 때, 특히 일관된 실행이 중요한 경우에 사용하십시오.

주요 제한 사항은 무엇인가요?

이 스킬은 원본 지침의 품질과 범위에 제약을 받습니다. 기본 문서가 불완전한 경우, 상담원이 추가적인 맥락 정보나 수동 검증이 필요할 수 있습니다.

GitHub에서 보기

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-injection skill 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

  1. Dependencies MUST be injected via constructors — NEVER use global variables or init() for service setup
  2. Small projects (< 10 services) SHOULD use manual constructor injection — no library needed
  3. Interfaces MUST be defined where consumed, not where implemented — accept interfaces, return structs
  4. NEVER use global registries or package-level service locators
  5. The DI container MUST only exist at the composition root (main() or app startup) — NEVER pass the container as a dependency
  6. Prefer lazy initialization — only create services when first requested
  7. Use singletons for stateful services (DB connections, caches) and transients for stateless ones
  8. Mock at the interface boundary — DI makes this trivial
  9. Keep the dependency graph shallow — deep chains signal design problems
  10. Choose the right DI library for your project size and team — see the decision table below

Why Dependency Injection?

Problem without DIHow DI solves it
Functions create their own dependenciesDependencies are injected — swap implementations freely
Testing requires real databases, APIsPass mock implementations in tests
Changing one component breaks othersLoose coupling via interfaces — components don't know each other's internals
Services initialized everywhereCentralized container manages lifecycle (singleton, factory, lazy)
All services loaded at startupLazy loading — services created only when first requested
Global state and init() functionsExplicit 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

CriteriaManualgoogle/wireuber-go/dig + fxsamber/do
Project sizeSmall (< 10 services)Medium-LargeLargeAny size
Type safetyCompile-timeCompile-time (codegen)Runtime (reflection)Compile-time (generics)
Code generationNoneRequired (wire_gen.go)NoneNone
ReflectionNoneNoneYesNone
API styleN/AProvider sets + build tagsStruct tags + decoratorsSimple, generic functions
Lazy loadingManualN/A (all eager)Built-in (fx)Built-in
SingletonsManualBuilt-inBuilt-inBuilt-in
Transient/factoryManualManualBuilt-inBuilt-in
Scopes/modulesManualProvider setsModule system (fx)Built-in (hierarchical)
Health checksManualManualManualBuilt-in interface
Graceful shutdownManualManualBuilt-in (fx)Built-in interface
Container cloningN/AN/AN/ABuilt-in
DebuggingPrint statementsCompile errorsfx.Visualize()ExplainInjector(), web interface
Go versionAnyAnyAny1.18+ (generics)
Learning curveNoneMediumHighLow

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

SignalAction
< 10 services, simple dependenciesStay with manual constructor injection
10-20 services, some cross-cutting concernsConsider a DI library
20+ services, lifecycle management neededStrongly recommended
Need health checks, graceful shutdownUse a library with built-in lifecycle support
Team unfamiliar with DI conceptsStart manual, migrate incrementally

Common Mistakes

MistakeFix
Global variables as dependenciesPass through constructors or DI container
init() for service setupExplicit initialization in main() or container
Depending on concrete typesAccept 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 requestOne container per application; use scopes for request-level isolation

Cross-References

  • → See samber/cc-skills-golang@golang-samber-do skill for detailed samber/do usage patterns
  • → See samber/cc-skills-golang@golang-structs-interfaces skill for interface design and composition
  • → See samber/cc-skills-golang@golang-testing skill for testing with dependency injection
  • → See samber/cc-skills-golang@golang-project-layout skill 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

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

복사 복사
빠른 설정: skill 폴더를 .claude/skills/로 복사하면 Claude가 해당 스킬을 자동으로 감지하여 사용합니다.

관련 스킬

nuxthub
업데이트 된 시간 2026년 8월 23일
tc-tracker
업데이트 된 시간 2026년 8월 27일
code-quality
업데이트 된 시간 2026년 8월 22일
altimate-data-engineering-skills
업데이트 된 시간 2026년 8월 23일
OR