Receber por email
← Voltar para o catálogo

golang-dependency-injection

Um guia completo sobre injeção de dependência (DI) em Golang, abordando sua importância, injeção manual por construtor e uma comparação de bibliotecas de DI populares. Utilize esta skill para projetar arquiteturas de serviço, configurar DI ou refatorar código.

11estrelas
Atualizado há 6 meses

Ver no GitHub ↗ Licença: NOASSERTION

Como adicionar

/plugin marketplace add yzfly/CTO-Skills

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/golang-dependency-injection/svg)](https://www.skillteca.com.br/skills/golang-dependency-injection)

Skills relacionadas

Ver todas de Desenvolvimento →

Alerta por categoria

Receba novas skills de Desenvolvimento toda segunda

1 email curto, só com as skills novas de Desenvolvimento. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

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 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](./references/manual-di.md) 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:

  • 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](./references/google-wire.md) — Compile-time code generation
  • [uber-go/dig + fx examples](./references/uber-dig-fx.md) — Reflection-based framework
  • [samber/do examples](./references/samber-do.md) — 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 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

Testing with DI

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.Ge
Compartilhar X LinkedIn