go-dependency-injection
About
This skill provides guidance on implementing dependency injection in Go, focusing on constructor injection and explicit wiring in `main()` to avoid global state. It explains when to use frameworks like Wire, Fx, or Dig versus manual dependency management. Use this skill for questions about wiring dependencies, improving testability, or removing singletons, but not for interface design or project structure topics.
Quick Install
Claude Code
Recommendednpx skills add eduardo-sl/go-agent-skills -a claude-code/plugin add https://github.com/eduardo-sl/go-agent-skillsgit clone https://github.com/eduardo-sl/go-agent-skills.git ~/.claude/skills/go-dependency-injectionCopy and paste this command in Claude Code to install this skill
Documentation
Go Dependency Injection
DI in Go is a pattern, not a framework: pass dependencies to
constructors, wire everything explicitly in main. Reach for a
framework only when manual wiring measurably hurts.
1. Constructor Injection — the Default
// ✅ Good — dependencies are explicit parameters
type OrderService struct {
repo OrderRepository
payments PaymentGateway
logger *slog.Logger
}
func NewOrderService(repo OrderRepository, payments PaymentGateway, logger *slog.Logger) *OrderService {
return &OrderService{repo: repo, payments: payments, logger: logger}
}
// ❌ Bad — hidden dependencies reached through globals
func (s *OrderService) Place(ctx context.Context, o Order) error {
db := database.Get() // global singleton
log.Printf("placing order") // global logger
// untestable without touching process-wide state
}
Rules:
- Accept interfaces for dependencies the service calls; return the concrete type from the constructor.
- Every dependency visible in the signature — if the list feels long, the type does too much (split it), don't hide deps to shorten it.
- Validate required deps in the constructor and return an error
(or accept a nil-safe default, e.g.
logger = slog.Default()).
2. The Composition Root
All wiring lives in one place — main (or a run function it calls).
Construction order is the dependency order, checked by the compiler:
func run(ctx context.Context, cfg Config) error {
db, err := store.Open(ctx, cfg.DatabaseURL)
if err != nil {
return fmt.Errorf("open db: %w", err)
}
defer db.Close()
orderRepo := store.NewOrderRepo(db)
payments := stripe.NewGateway(cfg.StripeKey)
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
orders := service.NewOrderService(orderRepo, payments, logger)
server := handler.NewServer(cfg.Addr, orders)
return server.ListenAndServe(ctx)
}
- No package builds its own dependencies; it receives them.
- No
init()wiring, no package-levelvar DB *sql.DB. - Two binaries needing different wiring = two mains, same components.
3. Eliminating Global State
// ❌ Before — package-level singleton
var defaultClient *api.Client
func Fetch(id string) (*Item, error) {
return defaultClient.Get(id)
}
// ✅ After — the dependency moves into a struct
type Fetcher struct {
client *api.Client
}
func NewFetcher(c *api.Client) *Fetcher { return &Fetcher{client: c} }
func (f *Fetcher) Fetch(id string) (*Item, error) {
return f.client.Get(id)
}
Migration path for a legacy codebase: introduce the struct, keep a
deprecated package-level wrapper delegating to one instance built in
main, move callers over, delete the wrapper.
Acceptable package-level state: pure constants, compiled regexps,
sync.Once-guarded process singletons that hold no config.
4. Function Dependencies for Small Seams
A full interface is overkill for one function — inject the function:
type Service struct {
now func() time.Time
genID func() string
publish func(ctx context.Context, e Event) error
}
// Production: Service{now: time.Now, genID: uuid.NewString, publish: bus.Publish}
// Test: Service{now: fixedTime, genID: constID, publish: capture}
5. When Frameworks Earn Their Complexity
Manual wiring scales further than expected — a 100-line run function
is still readable and compiler-checked. Consider a tool when wiring
crosses hundreds of components or many teams share one binary.
| Tool | Model | Trade-off |
|---|---|---|
| google/wire | Compile-time code generation | Wiring stays plain Go and compiler-checked; adds a codegen step |
| uber-go/fx | Runtime container + lifecycle | App lifecycle (start/stop hooks) managed; errors surface at runtime, magic in stack traces |
| uber-go/dig | Runtime container (fx's core) | Same runtime trade-offs, no lifecycle layer |
Decision rule: prefer manual wiring; if generation becomes necessary prefer wire (failures at compile time beat failures at startup); adopt fx only when you also want its lifecycle management and your team accepts the runtime container.
Never mix models: one composition root, one mechanism.
6. Wire Example (when chosen)
//go:build wireinject
func InitializeServer(cfg Config) (*handler.Server, error) {
wire.Build(
store.Open,
store.NewOrderRepo,
stripe.NewGateway,
service.NewOrderService,
handler.NewServer,
)
return nil, nil // replaced by generated code
}
wire generates the ordered constructor calls; the generated file is
committed and reviewed like handwritten code.
Verification Checklist
- Every service/handler receives dependencies via constructor parameters
- No package-level mutable singletons (
var DB,var logger,Get()accessors) - All wiring concentrated in main/run — no
init()construction - Dependencies accepted as interfaces (or funcs), concrete types returned
- Constructors validate required dependencies
- Components testable by passing fakes — no process-global setup in tests
- If a DI tool is used: exactly one, at the composition root only
go build ./...passes — wiring errors surface at compile time
GitHub Repository
Frequently asked questions
What is the go-dependency-injection skill?
go-dependency-injection is a Claude Skill by eduardo-sl. Skills package instructions and resources that Claude loads on demand, so Claude can perform go-dependency-injection-related tasks without extra prompting.
How do I install go-dependency-injection?
Use the install commands on this page: add go-dependency-injection to Claude Code as a plugin, or clone its repository into your skills directory, then restart Claude so it picks up the skill.
What category does go-dependency-injection belong to?
go-dependency-injection is in the Testing category, tagged ai, testing, and design.
Is go-dependency-injection free to use?
Yes. go-dependency-injection is listed on AIMCP and free to install.
Related Skills
This Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
This skill provides comprehensive knowledge for implementing Cloudflare Cron Triggers to schedule Workers using cron expressions. It covers setting up periodic tasks, maintenance jobs, and automated workflows while handling common issues like invalid cron expressions and timezone problems. Developers can use it for configuring scheduled handlers, testing cron triggers, and integrating with Workflows and Green Compute.
This Claude Skill provides a Playwright-based toolkit for testing local web applications through Python scripts. It enables frontend verification, UI debugging, screenshot capture, and log viewing while managing server lifecycles. Use it for browser automation tasks but run scripts directly rather than reading their source code to avoid context pollution.
This skill helps developers complete finished work by verifying tests pass and then presenting structured integration options. It guides the workflow for merging, creating PRs, or cleaning up branches after implementation is done. Use it when your code is ready and tested to systematically finalize the development process.
