Documentation
¶
Overview ¶
Package container provides a type-safe, generics-based dependency injection (service) container modelled on Laravel's container and NestJS's DI, made idiomatic for Go.
Services are keyed on their static type via reflect. Registration and resolution go through package-level generic functions so callers never type-assert:
type Config struct{ DSN string }
type DB struct{ cfg Config }
type UserRepo struct{ db *DB }
c := container.New()
container.Instance(c, Config{DSN: "postgres://…"})
container.Singleton(c, func(c *container.Container) (*DB, error) {
cfg, err := container.Make[Config](c)
if err != nil {
return nil, err
}
return &DB{cfg: cfg}, nil
})
container.Bind(c, func(c *container.Container) (*UserRepo, error) {
db, err := container.Make[*DB](c)
if err != nil {
return nil, err
}
return &UserRepo{db: db}, nil
})
repo := container.MustMake[*UserRepo](c)
Lifetimes:
- Bind — transient: the factory runs on every Make.
- Singleton — the factory runs at most once; the result is cached and shared. Caching is concurrency-safe: under concurrent Make the factory runs exactly once and every caller observes the same value.
- Instance — an already-built value, returned as-is on every Make.
Named bindings (BindNamed / MakeNamed) let several implementations of one type coexist under distinct names.
Scopes (c.Scope()) create child containers that inherit the parent's bindings and may override them. A singleton defined in the parent resolves to the parent's shared instance even when made from a child; a singleton defined (overridden) in a scope caches inside that scope, giving per-request lifetimes.
Build provides modest, opt-in reflection-based autowiring for struct types: it allocates the struct and fills each exported field whose type is already bound. See Build for the exact rules.
All exported operations are safe for concurrent use.
Example ¶
Example shows factory injection: a service that depends on a database connection, which in turn depends on configuration. The connection is a singleton (shared), the service is transient (built per resolve).
package main
import (
"fmt"
"github.com/devituz/lagodev/container"
)
// dbConfig and dbConn stand in for real config/database types to keep the
// example free of cross-package coupling.
type dbConfig struct{ DSN string }
type dbConn struct{ dsn string }
type userService struct{ db *dbConn }
func (s *userService) Greeting() string {
return "connected to " + s.db.dsn
}
// Example shows factory injection: a service that depends on a database
// connection, which in turn depends on configuration. The connection is a
// singleton (shared), the service is transient (built per resolve).
func main() {
c := container.New()
container.Instance(c, dbConfig{DSN: "postgres://localhost/app"})
container.Singleton(c, func(c *container.Container) (*dbConn, error) {
cfg, err := container.Make[dbConfig](c)
if err != nil {
return nil, err
}
return &dbConn{dsn: cfg.DSN}, nil
})
container.Bind(c, func(c *container.Container) (*userService, error) {
db, err := container.Make[*dbConn](c)
if err != nil {
return nil, err
}
return &userService{db: db}, nil
})
svc := container.MustMake[*userService](c)
fmt.Println(svc.Greeting())
}
Output: connected to postgres://localhost/app
Index ¶
- Variables
- func Bind[T any](c *Container, factory func(*Container) (T, error))
- func BindNamed[T any](c *Container, name string, factory func(*Container) (T, error))
- func Build[T any](c *Container) (T, error)
- func Instance[T any](c *Container, value T)
- func InstanceNamed[T any](c *Container, name string, value T)
- func Make[T any](c *Container) (T, error)
- func MakeNamed[T any](c *Container, name string) (T, error)
- func MustMake[T any](c *Container) T
- func MustMakeNamed[T any](c *Container, name string) T
- func Singleton[T any](c *Container, factory func(*Container) (T, error))
- func SingletonNamed[T any](c *Container, name string, factory func(*Container) (T, error))
- type Container
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotBound = errors.New("container: type not bound")
ErrNotBound is returned by Make/MakeNamed when no binding (in the container or any ancestor scope) matches the requested type and name.
Functions ¶
func Bind ¶
Bind registers a transient factory for type T: the factory runs on every Make[T]. Re-binding the same type (and name) replaces the prior binding.
func BindNamed ¶
BindNamed registers factory for type T under name, allowing several implementations of one type to coexist. Resolve with MakeNamed.
func Build ¶
Build allocates a value of struct type T and autowires its exported fields from the container. It is opt-in and intentionally modest.
Rules:
- T (after dereferencing one pointer level) must be a struct.
- For each exported field, the container is asked for a binding keyed on the field's exact type (unnamed). A field tagged `inject:"name"` resolves the named binding instead; `inject:"-"` is skipped.
- A field whose type is bound is set to the resolved value. A field whose type is not bound is left at its zero value, unless it is tagged `inject:""` or `inject:"name"` (explicitly requested), in which case the missing binding is a hard error.
- Unexported fields are never touched.
Build resolves through c (and its ancestors/overrides) and participates in cycle detection.
func Instance ¶
Instance registers an already-built value for type T. Every Make[T] returns value unchanged. Equivalent to a singleton that is already resolved.
func InstanceNamed ¶
InstanceNamed registers an already-built value for type T under name.
func Make ¶
Make resolves the value bound to type T. It returns ErrNotBound if no binding matches, propagates the factory's error, and returns a cyclic dependency error (carrying the type chain) instead of deadlocking when a factory depends, directly or transitively, on itself.
func MustMakeNamed ¶
MustMakeNamed is MakeNamed that panics on any error.
Types ¶
type Container ¶
type Container struct {
// contains filtered or unexported fields
}
Container is a dependency-injection container. The zero value is not usable; obtain one from New or, for a child scope, from Scope.
Safe for concurrent use.
func (*Container) Scope ¶
Scope returns a child container that inherits c's bindings. Bindings added to the child shadow the parent for resolutions made through the child, leaving the parent untouched. Singletons defined on an ancestor resolve to that ancestor's shared instance; singletons (re)defined on the scope cache within the scope, which is how per-request lifetimes are expressed.