di

package module
v0.24.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

README

di

Minimal, type-safe dependency injection for Go.

API

Registration
di.Register[T](func(b di.Builder[T]))

One call = one provider. Builder methods:

Method Description
b.New(func() (T, error)) Unnamed provider via constructor
b.Named(name, func() (T, error)) Named provider via constructor
b.Instance(val T) Pre-built instance (always singleton)
b.Extend(ptr *I) Alias another registered type

Each returns *Registration[T] for chaining:

Chain Description
.Scope(ScopeSingleton|ScopeTransient) Default: ScopeSingleton
.Multi() Include in ResolveAll
.OnStart(func(T) error) Lifecycle start hook
.OnStop(func(T) error) Lifecycle stop hook
Resolution
di.Resolve[T]() T                       // panics if not registered
di.ResolveNamed[T](name) T              // panics if not registered
di.TryResolve[T]() (T, bool)            // safe — returns false if missing
di.TryResolveNamed[T](name) (T, bool)   // safe — returns false if missing
di.ResolveAll[T]() []T                  // only Multi-marked entries
Lifecycle
di.StartAll() error
di.StartAllWithContext(ctx) error
di.StartAllWithTimeout(d) error
di.StopAll() error
di.StopAllWithContext(ctx) error
di.StopAllWithTimeout(d) error

StartAll runs OnStart hooks in registration order. On failure, already-started providers are rolled back. StopAll runs OnStop hooks in reverse order.

Testing
di.Reset() // clears all registrations and lifecycle state

Patterns

Singleton (default)
di.Register[Logger](func(b di.Builder[Logger]) {
    b.New(func() (Logger, error) { return &StdLogger{}, nil })
})

log := di.Resolve[Logger]() // same instance every call
Transient
di.Register[*Request](func(b di.Builder[*Request]) {
    b.New(func() (*Request, error) { return &Request{}, nil }).
        Scope(di.ScopeTransient)
})

r1 := di.Resolve[*Request]() // new instance
r2 := di.Resolve[*Request]() // new instance
Pre-built instance
cfg := &Config{Addr: ":8080"}
di.Register[*Config](func(b di.Builder[*Config]) {
    b.Instance(cfg)
})
Named + selector via env

Each implementation self-registers by name in init(). A selector promotes the configured one to the unnamed default:

// nats/nats.go
func init() {
    di.Register[Broker](func(b di.Builder[Broker]) {
        b.Named("[broker/nats]", func() (Broker, error) {
            return NewNatsBroker(config), nil
        }).OnStart(func(br Broker) error { return br.Connect() }).
           OnStop(func(br Broker) error { return br.Close() })
    })
}

// broker/broker.go
func Register() {
    switch env.Get("BROKER_PROVIDER", "nats") {
    case "nats":
        di.Register[Broker](func(b di.Builder[Broker]) {
            b.New(func() (Broker, error) {
                return di.ResolveNamed[Broker]("[broker/nats]"), nil
            })
        })
    }
}

// usage
broker := di.Resolve[Broker]()
Multi — capability aggregation

Register a type alias marked with Multi() to aggregate all implementations under a common interface:

// nats/nats.go
func init() {
    di.Register[Broker](func(b di.Builder[Broker]) {
        b.Named("[broker/nats]", ctor).OnStart(...).OnStop(...)
    })
    di.Register[Connectable](func(b di.Builder[Connectable]) {
        var broker Broker
        b.Extend(&broker).Multi()
    })
}

// db/postgres.go
func init() {
    di.Register[Database](func(b di.Builder[Database]) {
        b.Named("[db/postgres]", ctor).OnStart(...).OnStop(...)
    })
    di.Register[Connectable](func(b di.Builder[Connectable]) {
        var db Database
        b.Extend(&db).Multi()
    })
}

// startup health check — iterates all Multi-marked Connectables
for _, c := range di.ResolveAll[Connectable]() {
    if err := c.Ping(); err != nil { ... }
}
Optional dependency
if cache, ok := di.TryResolve[Cache](); ok {
    cache.Set(key, val)
}
Interface → concrete
di.Register[port.Broker](func(b di.Builder[port.Broker]) {
    b.New(func() (port.Broker, error) { return &NatsBroker{}, nil })
})

// resolves as interface
broker := di.Resolve[port.Broker]()

Examples

Runnable examples in examples/. All use //go:build ignore — run with go run:

basic.go — registration patterns
// singleton via interface
di.Register[Logger](func(b di.Builder[Logger]) {
    b.New(func() (Logger, error) { return &StdLogger{}, nil })
})

// pre-built instance — Logger injected manually
di.Register[Repo](func(b di.Builder[Repo]) {
    b.Instance(&MemRepo{
        data: map[int]string{1: "Alice", 2: "Bob"},
        log:  di.Resolve[Logger](),
    })
})

// named transient variant
di.Register[Logger](func(b di.Builder[Logger]) {
    b.Named("stderr", func() (Logger, error) { return &StdLogger{}, nil }).
        Scope(di.ScopeTransient)
})

log    := di.Resolve[Logger]()
repo   := di.Resolve[Repo]()
stderr := di.ResolveNamed[Logger]("stderr")

if cache, ok := di.TryResolve[Repo](); ok {
    fmt.Println(cache.Find(2))
}
go run ./examples/basic.go
lifecycle.go — OnStart/OnStop + graceful shutdown
di.Register[DB](func(b di.Builder[DB]) {
    b.New(func() (DB, error) { return &MemDB{}, nil }).
        OnStart(func(d DB) error { return d.(*MemDB).Connect() }).
        OnStop(func(d DB) error { return d.(*MemDB).Close() })
})

di.Register[Cache](func(b di.Builder[Cache]) {
    b.New(func() (Cache, error) { return &MemCache{}, nil }).
        OnStart(func(c Cache) error { return c.(*MemCache).Connect() }).
        OnStop(func(c Cache) error { return c.(*MemCache).Close() })
})

di.StartAll()
// ... app runs ...
di.StopAll() // reverse order: Cache stops before DB
go run ./examples/lifecycle.go
multi.go — init() + selector + capability aggregation
// nats/nats.go — self-registers by name
func init() {
    di.Register[Broker](func(b di.Builder[Broker]) {
        b.Named("[broker/nats]", ctor).OnStart(...).OnStop(...)
    })
    // opts in to Connectable aggregation
    di.Register[Connectable](func(b di.Builder[Connectable]) {
        var broker Broker
        b.Extend(&broker).Multi()
    })
}

// selector — promotes named to default
func registerBroker(provider string) {
    switch provider {
    case "nats":
        di.Register[Broker](func(b di.Builder[Broker]) {
            b.New(func() (Broker, error) {
                return di.ResolveNamed[Broker]("[broker/nats]"), nil
            })
        })
    }
}

// health check — only Multi-marked entries
for _, c := range di.ResolveAll[Connectable]() {
    c.Ping()
}
go run ./examples/multi.go

Notes

  • Instance ignores .Scope() — always singleton.
  • OnStart/OnStop only apply to singleton providers (transients have no cached instance to stop).
  • Circular singleton dependencies panic with a clear message.
  • Reset() is intended for tests only — do not call in production code.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Register

func Register[T any](configurator func(Builder[T]))

Register configures one provider for type T via the builder.

func Reset

func Reset()

Reset clears all registrations and lifecycle state. Use in tests.

func Resolve

func Resolve[T any]() T

Resolve returns the default (unnamed) instance of T. Panics if not registered.

func ResolveAll

func ResolveAll[T any]() []T

ResolveAll returns all instances of T marked with Multi().

func ResolveNamed added in v0.8.6

func ResolveNamed[T any](name string) T

ResolveNamed returns the named instance of T. Panics if not registered.

func StartAll added in v0.8.6

func StartAll() error

StartAll runs OnStart hooks in registration order.

func StartAllWithContext added in v0.8.6

func StartAllWithContext(ctx context.Context) error

StartAllWithContext runs OnStart hooks with context cancellation support.

func StartAllWithTimeout added in v0.8.6

func StartAllWithTimeout(d time.Duration) error

StartAllWithTimeout runs StartAllWithContext with a deadline.

func StopAll added in v0.8.6

func StopAll() error

StopAll runs OnStop hooks in reverse registration order.

func StopAllWithContext added in v0.8.6

func StopAllWithContext(ctx context.Context) error

StopAllWithContext runs OnStop hooks with context cancellation support.

func StopAllWithTimeout added in v0.8.6

func StopAllWithTimeout(d time.Duration) error

StopAllWithTimeout runs StopAllWithContext with a deadline.

func TryResolve added in v0.8.6

func TryResolve[T any]() (T, bool)

TryResolve returns the default instance and true, or zero value and false if not registered.

func TryResolveNamed added in v0.8.6

func TryResolveNamed[T any](name string) (T, bool)

TryResolveNamed returns the named instance and true, or zero value and false if not registered.

Types

type Builder added in v0.13.0

type Builder[T any] interface {
	New(ctor func() (T, error)) *Registration[T]
	Named(name string, ctor func() (T, error)) *Registration[T]
	Instance(val T) *Registration[T]
	Extend(ptr any) *Registration[T]
}

Builder[T] configures providers for type T.

type Registration added in v0.13.0

type Registration[T any] struct {
	// contains filtered or unexported fields
}

Registration is the fluent chain returned by builder methods.

func (*Registration[T]) Multi added in v0.13.0

func (r *Registration[T]) Multi() *Registration[T]

func (*Registration[T]) OnStart added in v0.13.0

func (r *Registration[T]) OnStart(fn func(T) error) *Registration[T]

func (*Registration[T]) OnStop added in v0.13.0

func (r *Registration[T]) OnStop(fn func(T) error) *Registration[T]

func (*Registration[T]) Scope added in v0.13.0

func (r *Registration[T]) Scope(s Scope) *Registration[T]

type Scope added in v0.13.0

type Scope int

Scope controls how instances are created.

const (
	ScopeSingleton Scope = iota // one shared instance (default)
	ScopeTransient              // new instance per resolution
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL