container

package
v0.22.0 Latest Latest
Warning

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

Go to latest
Published: Jun 24, 2026 License: MIT Imports: 7 Imported by: 0

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

Examples

Constants

This section is empty.

Variables

View Source
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

func Bind[T any](c *Container, factory func(*Container) (T, error))

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

func BindNamed[T any](c *Container, name string, factory func(*Container) (T, error))

BindNamed registers factory for type T under name, allowing several implementations of one type to coexist. Resolve with MakeNamed.

func Build

func Build[T any](c *Container) (T, error)

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

func Instance[T any](c *Container, value T)

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

func InstanceNamed[T any](c *Container, name string, value T)

InstanceNamed registers an already-built value for type T under name.

func Make

func Make[T any](c *Container) (T, error)

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 MakeNamed

func MakeNamed[T any](c *Container, name string) (T, error)

MakeNamed resolves the value bound to type T under name. See Make.

func MustMake

func MustMake[T any](c *Container) T

MustMake is Make for wiring/bootstrap code: it panics on any error.

func MustMakeNamed

func MustMakeNamed[T any](c *Container, name string) T

MustMakeNamed is MakeNamed that panics on any error.

func Singleton

func Singleton[T any](c *Container, factory func(*Container) (T, error))

Singleton registers factory for type T as a singleton: the factory runs at most once and the result is cached and shared. Resolution is concurrency-safe — under concurrent Make the factory runs exactly once.

func SingletonNamed

func SingletonNamed[T any](c *Container, name string, factory func(*Container) (T, error))

SingletonNamed registers a named singleton for type T. Like Singleton but addressed by name.

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 New

func New() *Container

New returns a fresh root container.

func (*Container) Scope

func (c *Container) Scope() *Container

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.

Jump to

Keyboard shortcuts

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