Dependency injection
Prefer explicit constructor wiring and small interfaces over framework-heavy dependency injection in Go.
Canonical guidance
- use plain constructors and explicit wiring first
- inject behavior boundaries, not every concrete dependency
- keep initialization visible in
mainor a small composition root
Use when
- wiring services and repositories
- testing call boundaries
- sharing infrastructure dependencies safely
Avoid
- framework-style containers as a default
- interface-per-struct designs
- hiding startup wiring behind reflection
Preferred pattern
type Service struct {
Repo UserRepo
Log *slog.Logger
}
func NewService(repo UserRepo, log *slog.Logger) *Service {
return &Service{Repo: repo, Log: log}
}
Anti-pattern
- adding a DI framework before simple constructor wiring becomes painful
Explanation: This anti-pattern is tempting because containers promise flexibility, but in Go they often hide control flow without reducing real complexity.
Why
- explicit wiring keeps ownership and startup behavior easy to inspect
Related pages
Sources
- Effective Go - Go Team
- Go Code Review Comments - Go Team
- Organizing Go code - Andrew Gerrand