Documentation
¶
Overview ¶
Package oak provides a lightweight, reflection-based dependency injection container for Go.
Oak uses constructor functions to wire dependencies automatically. Register constructors with the container, call [Container.Build] to validate the dependency graph, then retrieve fully-assembled objects with Resolve or ResolveNamed.
Quick Start ¶
c := oak.New() c.Register(NewLogger) c.Register(NewDatabase) c.Build() db, err := oak.Resolve[*Database](c)
Lifetimes ¶
Singleton (default) — one shared instance for the lifetime of the container.
Transient — a fresh instance on every [Container.Resolve] call.
c.Register(NewLogger, oak.WithLifetime(oak.Transient))
Named Providers ¶
When you need several implementations of the same return type, use named registration:
c.RegisterNamed("mysql", NewMySQLDB)
c.RegisterNamed("postgres", NewPostgresDB)
db, _ := oak.ResolveNamed[Database](c, "postgres")
Graceful Shutdown ¶
Singleton providers that implement io.Closer are automatically tracked during [Container.Build]. Call [Container.Shutdown] to close them in reverse dependency order:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := c.Shutdown(ctx); err != nil {
log.Println("shutdown error:", err)
}
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotBuilt is returned when Resolve is called before Build. ErrNotBuilt = errors.New("container not built") // ErrAlreadyBuilt is returned when Register or Build is called after the // container has already been built. ErrAlreadyBuilt = errors.New("container already built") // ErrProviderNotFound is returned when no provider is registered for the // requested type or name. ErrProviderNotFound = errors.New("provider not found") // ErrCircularDependency is returned when the dependency graph contains a // cycle. The error message includes the full chain. ErrCircularDependency = errors.New("circular dependency detected") // ErrDuplicateProvider is returned when a provider for the same type or // name is registered more than once. ErrDuplicateProvider = errors.New("duplicate provider") // ErrAlreadyShutdown is returned when [Container.Shutdown] is called // more than once. ErrAlreadyShutdown = errors.New("container already shut down") )
Functions ¶
func Resolve ¶
Resolve is a generic helper that resolves a typed provider from the container. It is the recommended way to retrieve values:
db, err := oak.Resolve[*Database](c)
Example ¶
package main
import (
"fmt"
"github.com/ARTM2000/oak"
)
// Types used in examples only.
type Logger struct{ Prefix string }
type Config struct{ DSN string }
type Database struct {
Config *Config
Logger *Logger
}
func main() {
c := oak.New()
_ = c.Register(func() *Config { return &Config{DSN: "postgres://localhost"} })
_ = c.Register(func() *Logger { return &Logger{Prefix: "app"} })
_ = c.Register(func(cfg *Config, log *Logger) *Database {
return &Database{Config: cfg, Logger: log}
})
_ = c.Build()
db, err := oak.Resolve[*Database](c)
if err != nil {
panic(err)
}
fmt.Println(db.Config.DSN)
fmt.Println(db.Logger.Prefix)
}
Output: postgres://localhost app
func ResolveNamed ¶
ResolveNamed is a generic helper that resolves a named provider from the container:
db, err := oak.ResolveNamed[*Database](c, "primary")
Example ¶
package main
import (
"fmt"
"github.com/ARTM2000/oak"
)
type Greeter interface {
Greet() string
}
type englishGreeter struct{}
func (g *englishGreeter) Greet() string { return "hello" }
type spanishGreeter struct{}
func (g *spanishGreeter) Greet() string { return "hola" }
func main() {
c := oak.New()
_ = c.RegisterNamed("en", func() Greeter { return &englishGreeter{} })
_ = c.RegisterNamed("es", func() Greeter { return &spanishGreeter{} })
_ = c.Build()
en, _ := oak.ResolveNamed[Greeter](c, "en")
es, _ := oak.ResolveNamed[Greeter](c, "es")
fmt.Println(en.Greet())
fmt.Println(es.Greet())
}
Output: hello hola
Types ¶
type Container ¶
type Container interface {
// Register adds a constructor to the container. The constructor must be a
// function with the signature func(deps...) T or func(deps...) (T, error).
// Dependencies are expressed as function parameters and resolved by type.
Register(constructor interface{}, opts ...Option) error
// RegisterNamed adds a named constructor. Named providers live in a
// separate namespace and are resolved via [Container.ResolveNamed] or
// the generic [ResolveNamed] helper.
RegisterNamed(name string, constructor interface{}, opts ...Option) error
// Build validates the full dependency graph — detecting missing providers
// and circular dependencies — and eagerly instantiates all [Singleton]
// providers. After Build succeeds the container is immutable; no further
// registrations are accepted.
Build() error
// Resolve returns the value for the given type. For [Singleton] providers
// the cached instance is returned; for [Transient] providers a new
// instance is constructed on each call. Prefer the generic [Resolve]
// helper over calling this method directly.
Resolve(t reflect.Type) (reflect.Value, error)
// ResolveNamed returns the value for the named provider. The requested
// type t must be assignable from the provider's return type. Prefer the
// generic [ResolveNamed] helper over calling this method directly.
ResolveNamed(name string, t reflect.Type) (reflect.Value, error)
// Shutdown gracefully closes all singleton providers that implement
// [io.Closer], in reverse dependency order (dependents are closed before
// their dependencies). The context controls the overall deadline; if it
// expires, remaining closers are skipped and the context error is
// included in the result.
//
// Shutdown is safe to call multiple times; subsequent calls return
// [ErrAlreadyShutdown]. It is the caller's responsibility to stop
// calling [Container.Resolve] before or during shutdown.
Shutdown(ctx context.Context) error
}
Container defines the interface for the dependency injection container. Use New to create an instance.
func New ¶
func New() Container
New creates an empty Container ready for registration.
Example ¶
package main
import (
"fmt"
"github.com/ARTM2000/oak"
)
// Types used in examples only.
type Logger struct{ Prefix string }
func main() {
c := oak.New()
_ = c.Register(func() *Logger { return &Logger{Prefix: "app"} })
if err := c.Build(); err != nil {
panic(err)
}
logger, _ := oak.Resolve[*Logger](c)
fmt.Println(logger.Prefix)
}
Output: app
type Lifetime ¶
type Lifetime int
Lifetime controls how many instances of a provider the container creates.
const ( // Singleton is the default lifetime. The constructor is called once during // [Container.Build] and the resulting instance is reused for every // subsequent [Container.Resolve] call. Singleton Lifetime = iota // Transient means a new instance is constructed on every // [Container.Resolve] call. Transient )
type Option ¶
type Option func(*provider)
Option configures a provider during registration.
func WithLifetime ¶
WithLifetime sets the Lifetime of the provider. The default is Singleton.
Example ¶
package main
import (
"fmt"
"github.com/ARTM2000/oak"
)
// Types used in examples only.
type Logger struct{ Prefix string }
func main() {
c := oak.New()
_ = c.Register(
func() *Logger { return &Logger{Prefix: "app"} },
oak.WithLifetime(oak.Transient),
)
_ = c.Build()
l1, _ := oak.Resolve[*Logger](c)
l2, _ := oak.Resolve[*Logger](c)
fmt.Println(l1 == l2)
}
Output: false