Skills に戻る
samber/cc-skills-golangチェック済み

SKILL DETAIL

golang-uber-dig

samber/cc-skills-golang/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.

インストール · 319出典を見る

Installation

npx skills add https://github.com/samber/cc-skills-golang --skill golang-uber-dig

スキルファイル

SKILL.md

最終同期 · 2026/10/10

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.

## Table of Contents

- [Decorate](#decorate)
- [Scopes](#scopes)
- [Optional Dependencies](#optional-dependencies)
- [Error Handling](#error-handling)
- [Visualization](#visualization)
- [Quick Reference](#quick-reference)
  - [Container](#container)
  - [Provide options](#provide-options)
  - [Container options](#container-options)
  - [Errors](#errors)

## 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.

## Table of Contents

- [HTTP server with route group](#http-server-with-route-group)
- [Two databases (read-write + read-only)](#two-databases-read-write--read-only)
- [Provide as interface (`dig.As`) to hide concrete types](#provide-as-interface-digas-to-hide-concrete-types)
- [Request-scoped dependencies](#request-scoped-dependencies)
- [Optional dependency for graceful degradation](#optional-dependency-for-graceful-degradation)
- [Decorate to add cross-cutting behavior](#decorate-to-add-cross-cutting-behavior)
- [DryRun for graph validation in tests](#dryrun-for-graph-validation-in-tests)
- [Visualizing a failed graph](#visualizing-a-failed-graph)

## 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`.

## Table of Contents

- [Per-test container](#per-test-container)
- [Shared test wiring](#shared-test-wiring)
- [Validate the production graph in CI](#validate-the-production-graph-in-ci)
- [Detecting cycles before deploy](#detecting-cycles-before-deploy)
- [Asserting a constructor's error path](#asserting-a-constructors-error-path)
- [Recovering from constructor panics](#recovering-from-constructor-panics)

## 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(), "[email protected]")
        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")
```
SKILL.md›
---
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, Codex or similar harness, and for projects using Golang.
metadata:
  author: samber
  version: "1.2.2"
  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__*
paths:
  - "**/*.go"
---

**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:**

- [pkg.go.dev/go.uber.org/dig](https://pkg.go.dev/go.uber.org/dig)
- [github.com/uber-go/dig](https://github.com/uber-go/dig)

This skill is not exhaustive — 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`), preferred 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.

```bash
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:

| Concern | dig | fx |
| --- | --- | --- |
| DI container | ✅ `dig.New()` | ✅ (embedded) |
| Lifecycle hooks | ❌ | ✅ `fx.Lifecycle` OnStart/OnStop |
| Module system | ❌ | ✅ `fx.Module` with scoped decorators |
| Signal-aware run loop | ❌ | ✅ `app.Run()` blocks on SIGINT/SIGTERM |
| Structured event logging | ❌ | ✅ `fx.WithLogger` / `fxevent` |
| Startup/shutdown timeout | ❌ | ✅ `fx.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

```go
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

```go
// 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 whose inputs are dependencies and whose 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:

```go
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:

```go
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`:

```go
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:

```go
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:

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

## Full Application Example

```go
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](./references/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

| Mistake | Fix |
| --- | --- |
| Passing the container into services | The 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 `Name` | dig errors at `Provide` time. Either name them, or merge into a single provider that returns a `dig.Out` result struct. |
| Ignoring `Provide` errors | Wrap each `Provide` with a `must` helper. A silent registration error becomes a missing-type error far later. |
| Using groups when ordering matters | Groups are unordered. If order matters (middleware chain, migration sequence), provide an explicit ordered slice with one constructor. |
| Constructors with side effects on import | Keep `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](./references/testing.md).

## Further Reading

- [advanced.md](./references/advanced.md) — Decorate, Scopes, optional deps, error helpers, Visualize, full Quick Reference
- [recipes.md](./references/recipes.md) — end-to-end examples: HTTP server with route group, two databases, request scopes, decorators, dry-run validation
- [testing.md](./references/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>.