返回 Skills
samber/cc-skills-golang· MIT 内容可用

golang-uber-dig

Implements dependency injection in Golang using uber-go/dig — reflection-based container, Provide/Invoke, dig.In/dig.Out parameter and result objects, named values, value groups, optional dependencies, scopes, and Decorate. Apply when using or adopting uber-go/dig, when the codebase imports `go.uber.org/dig`, or when wiring an application graph at startup. For higher-level lifecycle and modules, see `samber/cc-skills-golang@golang-uber-fx` skill.

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: golang-uber-dig description: "Implements dependency injection in Golang using uber-go/dig — reflection-based container, Provide/Invoke, dig.In/dig.Out parameter and result objects, named values, value groups, optional dependencies, scopes, and Decorate. Apply when using or adopting uber-go/dig, when the codebase imports go.uber.org/dig, or when wiring an application graph at startup. For higher-level lifecycle and modules, see samber/cc-skills-golang@golang-uber-fx skill." user-invocable: true license: MIT compatibility: Designed for Claude Code or similar AI coding agents, and for projects using Golang. metadata: author: samber version: "1.1.3" openclaw: emoji: "⛏" homepage: https://github.com/samber/cc-skills-golang requires: bins: - go install: [] skill-library-version: "1.19.0" allowed-tools: Read Edit Write Glob Grep Bash(go:) Bash(golangci-lint:) Bash(git:) Agent WebFetch mcp__context7__resolve-library-id mcp__context7__query-docs Bash(godig:) Bash(gopls:) LSP mcp__gopls__

Persona: You are a Go architect wiring an application graph with dig. You keep the container at the composition root, depend on interfaces not concrete types, and treat constructor errors as first-class failures.

Using uber-go/dig for Dependency Injection in Go

Reflection-based DI toolkit, designed to power application frameworks (it is the engine behind uber-go/fx) and resolve object graphs during startup.

Official Resources:

This skill is not exhaustive. Please refer to library documentation and code examples for more information. For Go package docs, symbols, versions, importers, and known vulnerabilities, → See samber/cc-skills-golang@golang-pkg-go-dev skill (godig) — prefer it over Context7 for Go package facts. To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See samber/cc-skills-golang@golang-gopls skill (gopls). Context7 remains a fallback for docs not indexed on pkg.go.dev.

go get go.uber.org/dig

dig vs. fx

fx is built on dig and shares the same container engine — the DI primitives (Provide, Invoke, In/Out structs, named values, value groups) are identical. fx.In/fx.Out are re-exports of dig.In/dig.Out.

What fx adds on top of dig:

Concerndigfx
DI containerdig.New()✅ (embedded)
Lifecycle hooksfx.Lifecycle OnStart/OnStop
Module systemfx.Module with scoped decorators
Signal-aware run loopapp.Run() blocks on SIGINT/SIGTERM
Structured event loggingfx.WithLogger / fxevent
Startup/shutdown timeoutfx.StartTimeout / fx.StopTimeout

Choose dig when you need the wiring graph only: CLI tools, libraries exposing a container to callers, test harnesses, or embedding DI into an existing app that manages its own lifecycle.

Choose fx for long-running services (HTTP servers, workers, daemons) — lifecycle and signal handling are non-negotiable there. See samber/cc-skills-golang@golang-uber-fx skill.

Container

import "go.uber.org/dig"

c := dig.New()

Useful options: dig.DeferAcyclicVerification() (faster startup), dig.RecoverFromPanics() (turn panics into dig.PanicError), dig.DryRun(true) (validate without invoking).

Provide and Invoke

// Register a constructor — lazy, only runs when its output is needed
err := c.Provide(func(cfg *Config) (*sql.DB, error) {
    return sql.Open("postgres", cfg.DSN)
})

// Pull a service out of the container by asking for it as a function parameter
err = c.Invoke(func(db *sql.DB) error {
    return db.Ping()
})

Constructors are lazy and memoized: each output type is built once and shared (singleton per container). Provide errors at registration if the constructor is malformed; Invoke returns the constructor's error wrapped with the dependency path that triggered it.

A dig constructor is any function. Inputs are dependencies, outputs are provided types. error (last return) signals construction failure. Follow "accept interfaces, return structs".

Parameter Objects with dig.In

Once a constructor has 4+ dependencies, embed dig.In to group them as struct fields and tag fields:

type HandlerParams struct {
    dig.In

    Logger *zap.Logger
    DB     *sql.DB
    Cache  *redis.Client `optional:"true"`           // zero value if not provided
    DBRO   *sql.DB       `name:"readonly"`           // named dependency
    Routes []http.Handler `group:"routes"`           // value group
}

func NewHandler(p HandlerParams) *Handler { /* ... */ }

Tags: name:"...", optional:"true", group:"...".

Result Objects with dig.Out

Return several values from one constructor and attach name/group tags to results:

type ConnResult struct {
    dig.Out

    ReadWrite *sql.DB `name:"primary"`
    ReadOnly  *sql.DB `name:"readonly"`
}

func NewConnections(cfg *Config) (ConnResult, error) { /* ... */ }

Named Values

Two providers of the same type collide. Disambiguate with dig.Name:

c.Provide(NewPrimaryDB,  dig.Name("primary"))
c.Provide(NewReadOnlyDB, dig.Name("readonly"))

Consume by adding name:"primary" / name:"readonly" to a dig.In field.

Value Groups

Many providers, one consumer slice — typical for HTTP handlers, health checks, migrations:

type RouteResult struct {
    dig.Out
    Handler http.Handler `group:"routes"`
}

func NewUserHandler(db *sql.DB) RouteResult { /* ... */ }
func NewPostHandler(db *sql.DB) RouteResult { /* ... */ }

type ServerParams struct {
    dig.In
    Routes []http.Handler `group:"routes"`
}

Flatten — append ,flatten (e.g. group:"routes,flatten") to unwrap a slice instead of nesting it. Group order is not guaranteed; if order matters, provide an explicit ordered slice from a single constructor.

Provide as Interface (dig.As)

Register a concrete constructor and expose it under one or more interfaces without a separate adapter:

c.Provide(NewPostgresDB, dig.As(new(Database), new(io.Closer)))
// Consumers ask for Database or io.Closer; *PostgresDB stays hidden.

Full Application Example

func main() {
    c := dig.New()

    must(c.Provide(NewConfig))
    must(c.Provide(NewLogger))
    must(c.Provide(NewDatabase))
    must(c.Provide(NewServer))

    err := c.Invoke(func(srv *http.Server) error {
        return srv.ListenAndServe()
    })
    if err != nil {
        log.Fatal(err)
    }
}

func must(err error) { if err != nil { panic(err) } }

dig has no built-in lifecycle. If you need OnStart/OnStop hooks, signal handling, and graceful shutdown, use fx — see samber/cc-skills-golang@golang-uber-fx skill.

For Decorate, Scopes, optional deps, error helpers, and Visualize, see advanced.md.

Best Practices

  1. Keep the container at the composition root — never pass *dig.Container as a parameter; treat it like a plumbing detail of main(). Service-locator patterns defeat the testability gains of DI.
  2. Depend on interfaces, not concrete types — lets you swap implementations in tests without touching production code, and lets you use dig.As to expose narrow interfaces from wide structs.
  3. Prefer parameter objects (dig.In structs) once a constructor has 4+ dependencies — call sites stay readable and adding a new dependency is a one-line change instead of a signature break.
  4. Group registration by module (one file per module that calls c.Provide for its types) — review and refactoring become a per-module concern, and you can extract a module into a fx.Module later without rewriting wiring.
  5. Validate the graph eagerly in tests — call c.Invoke against the composition root in CI to surface missing providers at boot time, not at first request. DryRun(true) skips constructor execution.
  6. Return errors from constructors instead of panicking — dig wraps them with the dependency path, which makes the failure point obvious.

Common Mistakes

MistakeFix
Passing the container into servicesThe container belongs to main(). Inject the typed dependencies a service needs; otherwise tests need to build a real container.
Two providers for the same type without Namedig errors at Provide time. Either name them, or merge into a single provider that returns a dig.Out result struct.
Ignoring Provide errorsWrap each Provide with a must helper. A silent registration error becomes a missing-type error far later.
Using groups when ordering mattersGroups are unordered. If order matters (middleware chain, migration sequence), provide an explicit ordered slice with one constructor.
Constructors with side effects on importKeep init() empty — start work only inside the constructor, after the graph is built.

Testing

dig containers are cheap — build a fresh one per test, override providers with Decorate, and call Invoke to drive the system. For full patterns (per-test wiring, shared helpers, graph validation in CI, asserting wire-time errors, recovering from constructor panics), see testing.md.

Further Reading

  • advanced.md — Decorate, Scopes, optional deps, error helpers, Visualize, full Quick Reference
  • recipes.md — end-to-end examples: HTTP server with route group, two databases, request scopes, decorators, dry-run validation
  • testing.md — testing patterns and graph validation

Cross-References

  • → See samber/cc-skills-golang@golang-uber-fx skill for application lifecycle, modules, and signal-aware Run() built on top of dig
  • → See samber/cc-skills-golang@golang-dependency-injection skill for DI concepts and library comparison
  • → See samber/cc-skills-golang@golang-samber-do skill for a generics-based alternative without reflection
  • → See samber/cc-skills-golang@golang-google-wire skill for compile-time DI (no runtime container)
  • → See samber/cc-skills-golang@golang-structs-interfaces skill for interface design patterns
  • → See samber/cc-skills-golang@golang-testing skill for general testing patterns

If you encounter a bug or unexpected behavior in uber-go/dig, open an issue at https://github.com/uber-go/dig/issues.

附带文件

evals/evals.json
[
  {
    "id": 1,
    "name": "param-objects-many-deps",
    "description": "Tests use of dig.In parameter objects when a constructor has many dependencies",
    "prompt": "I'm wiring a Go service with uber-go/dig. I have a NewServer constructor that needs *zap.Logger, *sql.DB, *redis.Client, *Config, and *MetricsRegistry. The signature is getting unwieldy. How should I clean it up?",
    "trap": "Without the skill, the model keeps the long signature or wraps args in an ad-hoc struct without dig.In, missing the parameter-object pattern that lets the container fill the fields.",
    "assertions": [
      {"id": "1.1", "text": "Embeds dig.In in the parameter struct"},
      {"id": "1.2", "text": "Constructor takes the params struct as a single argument"},
      {"id": "1.3", "text": "Does NOT just keep the long parameter list as the answer"},
      {"id": "1.4", "text": "Does NOT use a plain struct without dig.In (which would not be filled by the container)"},
      {"id": "1.5", "text": "Mentions readability/maintainability benefit (adding a new dep is a one-line change)"}
    ]
  },
  {
    "id": 2,
    "name": "value-groups-for-handlers",
    "description": "Tests value groups when many constructors must contribute to one slice",
    "prompt": "In my Go HTTP server wired with uber-go/dig, I have several constructors (NewUserHandler, NewPostHandler, NewHealthHandler) and a NewRouter that should consume all of them. Each handler is registered independently. How do I wire this without the router knowing which handlers exist?",
    "trap": "Without the skill, the model invokes each handler individually inside main() and passes a slice to NewRouter, missing the group:\"...\" tag that decouples producers from consumers.",
    "assertions": [
      {"id": "2.1", "text": "Each handler constructor returns a dig.Out struct (or uses dig.Group via Provide option) tagged with group:\"routes\" (or similar group name)"},
      {"id": "2.2", "text": "NewRouter consumes a dig.In with a slice field tagged group:\"routes\""},
      {"id": "2.3", "text": "Handlers are added with c.Provide — no manual slice assembly in main()"},
      {"id": "2.4", "text": "Does NOT manually assemble the handler slice and pass it to NewRouter"},
      {"id": "2.5", "text": "Mentions that group order is not guaranteed (or is silent on it; does NOT claim a specific order is guaranteed)"}
    ]
  },
  {
    "id": 3,
    "name": "named-values-multiple-dbs",
    "description": "Tests dig.Name for multiple instances of the same type",
    "prompt": "My Go app needs two *sql.DB connections — one to the primary write database and one to a read replica. Both use the same *sql.DB type. Show me how to register and consume them with uber-go/dig.",
    "trap": "Without the skill, the model wraps the two connections in different types (struct PrimaryDB / struct ReadOnlyDB) instead of using dig.Name on a single type.",
    "assertions": [
      {"id": "3.1", "text": "Uses dig.Name(\"...\") on c.Provide for at least one of the two databases (or uses dig.Out result tags name:\"...\")"},
      {"id": "3.2", "text": "Both providers register the same *sql.DB type, distinguished by name"},
      {"id": "3.3", "text": "Consumer uses dig.In with name:\"primary\" / name:\"readonly\" tags"},
      {"id": "3.4", "text": "Does NOT introduce wrapper types like type PrimaryDB *sql.DB just to disambiguate"},
      {"id": "3.5", "text": "Does NOT register both as plain *sql.DB without names (which dig rejects at Provide time)"}
    ]
  },
  {
    "id": 4,
    "name": "as-to-hide-concrete",
    "description": "Tests dig.As to expose only an interface to consumers",
    "prompt": "I have a *PostgresDB concrete struct in my Go code that has many internal fields and methods. I want consumers to depend only on a Database interface (Query, Exec). How do I register it with uber-go/dig so consumers can never accidentally see the concrete type?",
    "trap": "Without the skill, the model registers the constructor returning Database (interface) directly, which works but loses type info; or writes a separate adapter constructor — missing dig.As that does this in one line.",
    "assertions": [
      {"id": "4.1", "text": "Uses dig.As(new(Database)) as a Provide option on the *PostgresDB constructor"},
      {"id": "4.2", "text": "The constructor itself returns the concrete *PostgresDB"},
      {"id": "4.3", "text": "Consumers ask for the Database interface, not *PostgresDB"},
      {"id": "4.4", "text": "Does NOT write a separate wrapper/adapter constructor that just returns the interface"},
      {"id": "4.5", "text": "Mentions or demonstrates that only the interface is exposed in the graph"}
    ]
  },
  {
    "id": 5,
    "name": "scopes-for-request-locals",
    "description": "Tests scopes for per-request dependencies",
    "prompt": "In my Go web app wired with uber-go/dig, I have global services (logger, database) and per-request data (current user, request ID). How do I keep request-scoped values from leaking across concurrent requests?",
    "trap": "Without the skill, the model registers everything in the root container and uses context.Value for per-request data — missing dig.Scope which provides isolated child containers.",
    "assertions": [
      {"id": "5.1", "text": "Uses c.Scope(\"request\") (or root.Scope) to create a child container per request"},
      {"id": "5.2", "text": "Global services (logger, database) stay registered on the root"},
      {"id": "5.3", "text": "Per-request providers are registered on the request scope, not on the root"},
      {"id": "5.4", "text": "Mentions that the child scope inherits root providers"},
      {"id": "5.5", "text": "Does NOT register per-request providers globally where they would be shared across requests"}
    ]
  },
  {
    "id": 6,
    "name": "container-not-passed-around",
    "description": "Tests that the container stays at the composition root",
    "prompt": "I'm using uber-go/dig in my Go service. My UserHandler depends on UserService and AuditLogger. Should I inject *dig.Container into UserHandler so it can resolve its own dependencies on demand?",
    "trap": "Without the skill, the model agrees to pass the container, turning it into a service-locator anti-pattern that hides dependencies and breaks testability.",
    "assertions": [
      {"id": "6.1", "text": "Advises against passing *dig.Container into business code"},
      {"id": "6.2", "text": "States that the container should only live at the composition root (main / startup)"},
      {"id": "6.3", "text": "Recommends UserHandler take typed dependencies (UserService, AuditLogger) as constructor parameters"},
      {"id": "6.4", "text": "Mentions service locator anti-pattern OR explains the testability/visibility downside"},
      {"id": "6.5", "text": "Does NOT show example code that injects the container into a handler"}
    ]
  },
  {
    "id": 7,
    "name": "graph-validation-dryrun",
    "description": "Tests dig.DryRun for validating the graph in CI without running constructors",
    "prompt": "I want a Go test that catches missing-provider errors and cycles in my uber-go/dig wiring without actually starting database connections, HTTP servers, or any real side effects. How do I write that test?",
    "trap": "Without the skill, the model spins up a real container with real constructors, or skips validation entirely — missing dig.DryRun(true) which validates types without invocation.",
    "assertions": [
      {"id": "7.1", "text": "Uses dig.New(dig.DryRun(true)) to build the test container"},
      {"id": "7.2", "text": "Registers the same Provides as the production binary"},
      {"id": "7.3", "text": "Calls c.Invoke against the composition root (or top-level dependency) to trigger validation"},
      {"id": "7.4", "text": "Asserts no error is returned"},
      {"id": "7.5", "text": "Does NOT actually instantiate real services in the test"}
    ]
  },
  {
    "id": 8,
    "name": "decorate-not-rewrite",
    "description": "Tests Decorate to wrap an existing value (e.g., logger) instead of replacing the constructor",
    "prompt": "In my Go app using uber-go/dig, I want every component in the 'worker' module to receive a *zap.Logger that is named 'worker' (i.e., adds a 'logger':'worker' field). Other modules should keep the unnamed logger. What's the cleanest way?",
    "trap": "Without the skill, the model rewrites NewLogger or wraps every constructor manually — missing c.Decorate which transforms the value at the scope/module boundary.",
    "assertions": [
      {"id": "8.1", "text": "Uses Decorate (c.Decorate or scope.Decorate) on *zap.Logger"},
      {"id": "8.2", "text": "The decorator returns log.Named(\"worker\") (or equivalent)"},
      {"id": "8.3", "text": "Decorate is applied at the worker scope/module, not globally"},
      {"id": "8.4", "text": "Does NOT modify or duplicate the original NewLogger constructor"},
      {"id": "8.5", "text": "Mentions decorator scope semantics (applies to scope and descendants only) OR places Decorate inside a scope"}
    ]
  },
  {
    "id": 9,
    "name": "panic-recovery-option",
    "description": "Tests RecoverFromPanics container option",
    "prompt": "Some third-party constructors I'm registering with uber-go/dig occasionally panic on invalid configuration. I want my Go app to convert those panics into error returns from c.Invoke instead of crashing the process. How do I configure the container?",
    "trap": "Without the skill, the model wraps every constructor in defer/recover — missing dig.RecoverFromPanics() which does this at the container level.",
    "assertions": [
      {"id": "9.1", "text": "Uses dig.New(dig.RecoverFromPanics()) (or passes the option to dig.New)"},
      {"id": "9.2", "text": "Mentions or shows that the panic surfaces as a typed dig.PanicError"},
      {"id": "9.3", "text": "Does NOT recommend wrapping every constructor in defer recover() manually"},
      {"id": "9.4", "text": "Uses errors.As(err, &dig.PanicError{}) or equivalent to detect the panic case"}
    ]
  },
  {
    "id": 10,
    "name": "groups-flatten-tag",
    "description": "Tests group:\",flatten\" tag when one constructor produces multiple group entries",
    "prompt": "I have a NewMigrations constructor in my Go app (using uber-go/dig) that returns a slice of Migration values. I want every Migration in this slice to be visible to consumers that consume the 'migrations' group as []Migration. How do I do this without nesting?",
    "trap": "Without the skill, the model registers []Migration as a single group entry, leaving consumers with [][]Migration. The right answer is the ',flatten' tag on the group.",
    "assertions": [
      {"id": "10.1", "text": "Uses group:\"migrations,flatten\" tag (with the flatten suffix)"},
      {"id": "10.2", "text": "Result struct has a slice field []Migration with the tag"},
      {"id": "10.3", "text": "Consumer receives []Migration (flat slice), not [][]Migration"},
      {"id": "10.4", "text": "Does NOT silently produce a nested-slice consumer signature"}
    ]
  },
  {
    "id": 11,
    "name": "fx-vs-dig-when-lifecycle",
    "description": "Tests recommending fx (instead of raw dig) when the user needs lifecycle/signal handling",
    "prompt": "I'm starting a new Go service with uber-go/dig. The service needs to gracefully shut down on SIGTERM, run startup migrations, and start a background worker. Should I implement signal handling and start/stop sequencing on top of dig myself?",
    "trap": "Without the skill, the model writes custom signal handling code on top of raw dig — missing that uber-go/fx is built specifically for this and provides fx.Lifecycle, fx.Hook, and signal-aware Run().",
    "assertions": [
      {"id": "11.1", "text": "Recommends migrating to or considering uber-go/fx for the lifecycle requirements"},
      {"id": "11.2", "text": "Mentions that fx is built on top of dig (so existing wiring patterns transfer)"},
      {"id": "11.3", "text": "Mentions fx.Lifecycle / fx.Hook (OnStart/OnStop) for graceful boot/shutdown"},
      {"id": "11.4", "text": "Mentions app.Run() handling SIGINT/SIGTERM"},
      {"id": "11.5", "text": "Does NOT walk the user through writing custom signal handling on top of raw dig as the primary recommendation"}
    ]
  }
]
references/advanced.md
# Advanced — uber-go/dig

Detail topics that are referenced from `SKILL.md`. Each section is self-contained.

## Decorate

`Decorate` modifies a value already provided in the container — the decorator receives the original instance and returns a replacement. Common uses: enriching a logger with context, wrapping a metrics scope with tags, swapping a real client for a recording one in a child scope.

```go
c.Decorate(func(log *zap.Logger) *zap.Logger {
    return log.Named("worker")
})
```

Decorators apply to the scope they were registered in and to that scope's descendants. Use them at scope boundaries or package wiring boundaries, not in main(), so changes stay local.

## Scopes

A `Scope` is a child container that inherits providers from its parent and can add or override its own. Scopes let request-, tenant-, or module-level dependencies coexist with shared singletons:

```go
root := dig.New()
root.Provide(NewLogger)
root.Provide(NewDatabase)

requestScope := root.Scope("request")
requestScope.Provide(NewRequestContext)            // only visible inside requestScope
requestScope.Decorate(func(l *zap.Logger) *zap.Logger {
    return l.With(zap.String("scope", "request"))
})
```

By default, providers registered to a scope are private to that scope and its children. Pass `dig.Export(true)` to `Provide` inside a scope to make the type visible from the parent:

```go
requestScope.Provide(NewSharedCache, dig.Export(true))
```

## Optional Dependencies

`optional:"true"` lets a consumer compile and run when a provider is missing. Use it sparingly — optional dependencies hide configuration mistakes. They make sense for genuinely optional features (a tracing exporter, an in-memory cache) but not for core services like a database.

```go
type Params struct {
    dig.In

    Logger *zap.Logger
    Tracer trace.Tracer `optional:"true"`
}
```

## Error Handling

dig wraps the constructor error with the dependency path so you can see _which_ graph edge failed:

```go
if err := c.Invoke(run); err != nil {
    // err describes the chain: "could not build *http.Server: ..."
}
```

Useful helpers:

- `errors.As(err, &dig.Error{})` — true if the error originated inside dig
- `dig.RootCause(err)` — unwrap to the original constructor error returned by user code
- `dig.IsCycleDetected(err)` — true if the graph contains a cycle (typically reported at first `Invoke` unless `DeferAcyclicVerification` is set)
- `errors.As(err, &dig.PanicError{})` — when `RecoverFromPanics` is enabled, a panicking constructor surfaces as this typed error

## Visualization

dig can emit the dependency graph in DOT format — useful when wiring becomes too tangled to reason about by reading code:

```go
f, _ := os.Create("graph.dot")
_ = dig.Visualize(c, f)
// then: dot -Tpng graph.dot -o graph.png
```

`dig.VisualizeError(err)` highlights the failed edges when an `Invoke` returns an error — invaluable for debugging "missing type" failures in deep graphs.

## Quick Reference

### Container

| Function/Method | Purpose |
| --- | --- |
| `dig.New(opts...)` | Create a root container |
| `c.Provide(ctor, opts...)` | Register a constructor |
| `c.Invoke(fn, opts...)` | Run a function with injected dependencies |
| `c.Decorate(fn, opts...)` | Modify a previously-provided value within a scope |
| `c.Scope(name, opts...)` | Create a child scope (private providers by default) |
| `c.String()` | Human-readable text summary of providers (not DOT; use `dig.Visualize` for DOT) |

### Provide options

| Option | Purpose |
| --- | --- |
| `dig.Name("...")` | Disambiguate same-typed providers |
| `dig.Group("...")` | Add the result to a value group |
| `dig.As(new(I))` | Provide the concrete value as one or more interfaces |
| `dig.Export(true)` | Make a scope-level provider visible from the root |
| `dig.FillProvideInfo(&info)` | Capture metadata for tooling |

### Container options

| Option | Purpose |
| --- | --- |
| `dig.DeferAcyclicVerification()` | Defer cycle check to first `Invoke` |
| `dig.RecoverFromPanics()` | Convert constructor panics into `dig.PanicError` |
| `dig.DryRun(true)` | Validate without invoking constructors |

### Errors

| Helper                              | Purpose                           |
| ----------------------------------- | --------------------------------- |
| `dig.RootCause(err)`                | Unwrap to the user-returned error |
| `dig.IsCycleDetected(err)`          | True if the graph has a cycle     |
| `errors.As(err, &dig.PanicError{})` | Detect a recovered panic          |
| `dig.Visualize(c, w, opts...)`      | Write the graph in DOT format     |
references/recipes.md
# Recipes — uber-go/dig

End-to-end examples that go beyond the SKILL.md basics. Each recipe is self-contained and shows a real wiring problem.

## HTTP server with route group

```go
package main

import (
    "fmt"
    "log"
    "net/http"

    "go.uber.org/dig"
)

// Each handler contributes one route to the "routes" group.
type RouteResult struct {
    dig.Out
    Route Route `group:"routes"`
}

type Route struct {
    Pattern string
    Handler http.Handler
}

func NewHealthRoute() RouteResult {
    return RouteResult{Route: Route{
        Pattern: "/health",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
            w.WriteHeader(http.StatusOK)
        }),
    }}
}

func NewUserRoute(repo *UserRepo) RouteResult {
    return RouteResult{Route: Route{
        Pattern: "/users",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            users, err := repo.List(r.Context())
            if err != nil {
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
                return
            }
            fmt.Fprintf(w, "%d users", len(users))
        }),
    }}
}

// The server consumes every Route registered to "routes".
type ServerParams struct {
    dig.In
    Routes []Route `group:"routes"`
}

func NewServer(p ServerParams) *http.Server {
    mux := http.NewServeMux()
    for _, r := range p.Routes {
        mux.Handle(r.Pattern, r.Handler)
    }
    return &http.Server{Addr: ":8080", Handler: mux}
}

func main() {
    c := dig.New()

    must(c.Provide(NewDB))           // *sql.DB
    must(c.Provide(NewUserRepo))     // *UserRepo
    must(c.Provide(NewHealthRoute))  // adds to group
    must(c.Provide(NewUserRoute))    // adds to group
    must(c.Provide(NewServer))

    err := c.Invoke(func(srv *http.Server) error {
        log.Println("listening on", srv.Addr)
        return srv.ListenAndServe()
    })
    if err != nil {
        log.Fatal(err)
    }
}

func must(err error) {
    if err != nil {
        panic(err)
    }
}
```

## Two databases (read-write + read-only)

```go
type DBResult struct {
    dig.Out
    Primary  *sql.DB `name:"primary"`
    ReadOnly *sql.DB `name:"readonly"`
}

func NewDatabases(cfg *Config) (DBResult, error) {
    rw, err := sql.Open("postgres", cfg.PrimaryDSN)
    if err != nil {
        return DBResult{}, fmt.Errorf("primary: %w", err)
    }
    ro, err := sql.Open("postgres", cfg.ReadOnlyDSN)
    if err != nil {
        rw.Close()
        return DBResult{}, fmt.Errorf("readonly: %w", err)
    }
    return DBResult{Primary: rw, ReadOnly: ro}, nil
}

type RepoParams struct {
    dig.In
    Writer *sql.DB `name:"primary"`
    Reader *sql.DB `name:"readonly"`
}

func NewUserRepo(p RepoParams) *UserRepo {
    return &UserRepo{w: p.Writer, r: p.Reader}
}
```

## Provide as interface (`dig.As`) to hide concrete types

```go
type Cache interface {
    Get(key string) (string, bool)
    Set(key, value string)
}

type RedisCache struct {
    client *redis.Client
    metrics *Metrics // an internal field consumers should not see
}

func NewRedisCache(client *redis.Client, m *Metrics) *RedisCache {
    return &RedisCache{client: client, metrics: m}
}

func (c *RedisCache) Get(key string) (string, bool) { /* ... */ }
func (c *RedisCache) Set(key, value string)         { /* ... */ }

func main() {
    c := dig.New()
    must(c.Provide(NewRedisClient))
    must(c.Provide(NewMetrics))
    // Consumers see Cache, never *RedisCache or its internals.
    must(c.Provide(NewRedisCache, dig.As(new(Cache))))
    must(c.Invoke(func(cache Cache) {
        cache.Set("hello", "world")
    }))
}
```

## Request-scoped dependencies

A child scope inherits its parent's providers but adds request-local ones:

```go
root := dig.New()
must(root.Provide(NewLogger))
must(root.Provide(NewDB))
must(root.Provide(NewHandler)) // *Handler is shared; the scope inherits it

func handle(w http.ResponseWriter, req *http.Request) {
    scope := root.Scope("request")

    // Request-scoped values
    must(scope.Provide(func() *http.Request { return req }))
    must(scope.Provide(func() RequestID { return RequestID(req.Header.Get("X-Request-ID")) }))
    must(scope.Decorate(func(l *zap.Logger) *zap.Logger {
        return l.With(zap.String("request_id", req.Header.Get("X-Request-ID")))
    }))

    err := scope.Invoke(func(h *Handler) error {
        return h.Serve(w, req)
    })
    if err != nil {
        http.Error(w, err.Error(), 500)
    }
}
```

The decorator only applies inside the request scope — sibling scopes (other in-flight requests) keep their own logger.

## Optional dependency for graceful degradation

```go
type WorkerParams struct {
    dig.In

    DB     *sql.DB
    Tracer trace.Tracer `optional:"true"` // app still boots without OTel
}

func NewWorker(p WorkerParams) *Worker {
    w := &Worker{db: p.DB}
    if p.Tracer != nil {
        w.tracer = p.Tracer
    } else {
        w.tracer = trace.NewNoopTracerProvider().Tracer("noop")
    }
    return w
}
```

Reach for `optional` only when the dependency is genuinely optional — a missing DB hidden behind `optional` becomes a nil-pointer panic at first use.

## Decorate to add cross-cutting behavior

```go
// Wrap the *sql.DB with a metrics-recording wrapper everywhere.
must(c.Decorate(func(db *sql.DB, m *Metrics) *sql.DB {
    return wrapWithMetrics(db, m)
}))

// Wrap the logger with service tags.
must(c.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger {
    return log.With(
        zap.String("service", cfg.ServiceName),
        zap.String("env", cfg.Env),
    )
}))
```

Decorators are scope-local. A decorator on the root applies everywhere; a decorator on a child scope only applies to that subtree.

## DryRun for graph validation in tests

```go
func TestWiringIsValid(t *testing.T) {
    c := dig.New(dig.DryRun(true))

    // Register everything main() registers
    must := func(err error) {
        require.NoError(t, err)
    }
    must(c.Provide(NewConfig))
    must(c.Provide(NewLogger))
    must(c.Provide(NewDB))
    must(c.Provide(NewServer))

    // Invoke the composition root: dig validates types without running constructors.
    require.NoError(t, c.Invoke(func(*http.Server) {}))
}
```

This catches "no provider for \*X" failures at build time instead of in production.

## Visualizing a failed graph

```go
err := c.Invoke(run)
if err != nil {
    f, _ := os.Create("graph.dot")
    defer f.Close()
    _ = dig.Visualize(c, f, dig.VisualizeError(err))
    log.Fatalf("wiring failed (graph in graph.dot): %v", err)
}
// Render: dot -Tpng graph.dot -o graph.png
```

`VisualizeError` highlights the missing edges in red — much faster than reading the wrapped error chain.
references/testing.md
# Testing with uber-go/dig

dig containers are cheap to create. Build a fresh one per test, override what you need, drive the system with `Invoke`.

## Per-test container

```go
func TestUserService_Create(t *testing.T) {
    c := dig.New()

    fakeDB := &fakeDatabase{}
    require.NoError(t, c.Provide(func() Database { return fakeDB }))
    require.NoError(t, c.Provide(NewUserService))

    require.NoError(t, c.Invoke(func(s *UserService) {
        err := s.Create(context.Background(), "alice@example.com")
        require.NoError(t, err)
    }))

    require.Len(t, fakeDB.inserted, 1)
}
```

## Shared test wiring

For larger suites, factor the common providers into a helper:

```go
func newTestContainer(t *testing.T, overrides ...func(*dig.Container)) *dig.Container {
    t.Helper()
    c := dig.New()
    require.NoError(t, c.Provide(NewTestLogger))
    require.NoError(t, c.Provide(NewInMemoryCache))
    require.NoError(t, c.Provide(func() Database { return &fakeDatabase{} }))
    require.NoError(t, c.Provide(NewUserService))

    for _, override := range overrides {
        override(c)
    }
    return c
}

func TestUserService_NotFound(t *testing.T) {
    c := newTestContainer(t, func(c *dig.Container) {
        // Replace the default DB with one that returns sql.ErrNoRows.
        require.NoError(t, c.Decorate(func(db Database) Database {
            return &notFoundDB{Database: db}
        }))
    })

    require.NoError(t, c.Invoke(func(s *UserService) {
        _, err := s.Get(context.Background(), "missing")
        require.ErrorIs(t, err, ErrUserNotFound)
    }))
}
```

`Decorate` is the cleanest way to swap a dependency in tests — the test reads almost like production wiring with one extra line.

## Validate the production graph in CI

```go
func TestProductionGraph(t *testing.T) {
    c := dig.New(dig.DryRun(true))

    // Replicate every Provide() from main()
    require.NoError(t, registerAll(c))

    // Invoke the same root the production binary does
    require.NoError(t, c.Invoke(func(*http.Server, *Worker, *MetricsExporter) {}))
}
```

`DryRun(true)` skips constructor execution — the graph is validated structurally. This catches missing-provider and type-mismatch errors without spinning up real DB connections.

## Detecting cycles before deploy

A cyclic graph fails at the first `Invoke` (or at `Provide` time when `DeferAcyclicVerification` is off, which is the default):

```go
func TestNoCycles(t *testing.T) {
    c := dig.New()
    require.NoError(t, registerAll(c))

    err := c.Invoke(func(*App) {})
    require.False(t, dig.IsCycleDetected(err), "cycle in dependency graph: %v", err)
}
```

## Asserting a constructor's error path

When a constructor returns an error, dig wraps it with the dependency path. Use `dig.RootCause` to assert the original error:

```go
func TestDBProvider_BadDSN(t *testing.T) {
    c := dig.New()
    require.NoError(t, c.Provide(func() *Config {
        return &Config{DSN: "not a dsn"}
    }))
    require.NoError(t, c.Provide(NewDB))

    err := c.Invoke(func(*sql.DB) {})
    require.Error(t, err)
    require.ErrorContains(t, dig.RootCause(err), "invalid connection string")
}
```

## Recovering from constructor panics

Wrap constructors that may panic on misuse so the test reports a typed error instead of crashing the runner:

```go
c := dig.New(dig.RecoverFromPanics())

require.NoError(t, c.Provide(func() *App {
    panic("intentionally broken")
}))

err := c.Invoke(func(*App) {})

var pe dig.PanicError
require.True(t, errors.As(err, &pe))
require.Contains(t, pe.Error(), "intentionally broken")
```
    golang-uber-dig | Prompt Minder