# Golang Dependency Injection

> Design, simplify, or review Go dependency wiring: constructors, composition roots, interface boundaries, lifecycle ownership, test substitution, and DI tool tradeoffs. Use for manual injection, container evaluation, or an existing generated/runtime dependency graph.

- Skill: `reagin/golang-dependency-injection` (Agent Skill)
- Install (CLI): `npx skillmds@latest add reagin/golang-dependency-injection`
- Raw SKILL.md: https://api.skillmd.com/api/skills/reagin/golang-dependency-injection/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: reagin (https://skillmd.com/u/reagin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/reagin/golang-dependency-injection

---


# Dependency Injection in Go

Make dependencies and lifecycle ownership visible. Prefer the simplest wiring that fits the existing application.

## Inspect the graph first

Read constructors, startup code, shutdown paths, tests, and any existing container or code-generation configuration. Sketch:

- long-lived resources such as database pools, clients, queues, and servers;
- which component creates and closes each resource;
- optional or environment-specific implementations;
- cycles, hidden globals, and dependencies retrieved at runtime;
- test seams that are actually used.

Do not rewrite working construction code merely to impose a library or a uniform constructor shape.

## Default approach

Manual constructor injection is usually the clearest baseline:

- constructors accept required collaborators explicitly;
- configuration is parsed and validated before construction;
- startup code composes concrete implementations;
- cleanup ownership follows construction ownership;
- callers receive useful construction errors rather than partially initialized objects.

Use interfaces where the consumer needs behavioral abstraction, multiple implementations exist, or a meaningful test boundary benefits. Do not create an interface for every struct, and do not move a consumer-specific interface into an implementation package merely for centralization.

Avoid package initialization for fallible setup, mutable package globals, and service locators. Passing a container into business components hides dependencies and makes behavior order-dependent.

## Composition and lifecycle

Keep wiring at a small number of composition roots, normally an executable or application bootstrap package. It is reasonable to divide large wiring code into feature modules when ownership remains explicit.

For resources with lifecycle:

1. Construct in dependency order.
2. If later construction fails, close already-created resources.
3. Start components only after required construction succeeds.
4. Stop intake before closing dependencies used by in-flight work.
5. Shut down in reverse dependency order with a bounded context.
6. Preserve and report meaningful startup and shutdown errors.

Do not assume every dependency should be lazy or singleton. Use laziness only when startup cost, optional features, or failure isolation justify it. Share stateful clients and pools according to their documented concurrency model; create request-scoped values at request boundaries.

## When a DI tool may help

Keep the project's existing DI technology unless the user is evaluating a change. Before adopting a tool, verify its current maintenance status and official documentation, then compare:

| Need | Relevant tradeoff |
| --- | --- |
| Compile-time generated wiring | Generated files and build workflow versus early type errors |
| Runtime container | Less handwritten wiring versus runtime graph errors and reflection |
| Application lifecycle framework | Integrated hooks and modules versus stronger framework ownership |
| Plain constructors | Maximum transparency versus more manual composition in large graphs |

A service count is not a reliable adoption threshold. Base the decision on graph complexity, optional modules, lifecycle coordination, team familiarity, debugging, and build constraints.

Treat a new DI dependency, generator, or build step as an architectural choice. If a generator is already used, update its source definitions and regenerate with the pinned project command; do not hand-edit generated output.

## Testing

Test application behavior through small consumer-owned interfaces or fakes where substitution is useful. Avoid tests that only prove a container can resolve every type unless graph validation is a supported, meaningful failure mode.

Useful checks include:

- constructors reject invalid configuration;
- startup failure cleans up prior resources;
- shutdown order and cancellation are correct;
- alternate implementations satisfy the same behavior;
- wiring contains no cycles or missing registrations;
- test overrides cannot leak across parallel cases.

## Review checklist

- Dependencies are visible at construction rather than fetched globally.
- Interface boundaries follow behavior needed by consumers.
- The composition root does not leak into domain code.
- Resource ownership and shutdown order are unambiguous.
- Optional dependencies are modeled explicitly instead of as surprising nil values.
- Constructors do not perform unrelated hidden work.
- A DI framework earns its operational and debugging cost.
- Generated wiring is reproducible with repository-pinned commands.

