Documentation
¶
Overview ¶
Package ioc provides a powerful and lightweight Inversion of Control (IoC) container for Go applications with comprehensive dependency injection capabilities.
Package ioc provides a powerful and lightweight Inversion of Control (IoC) container for Go applications with comprehensive dependency injection capabilities.
Package ioc provides a powerful and lightweight Inversion of Control (IoC) container for Go applications with comprehensive dependency injection capabilities.
This file defines error types and error formatting functions used throughout the IoC container system to provide consistent and detailed error messages.
Package ioc provides a powerful and lightweight Inversion of Control (IoC) container for Go applications with comprehensive dependency injection capabilities.
Package ioc provides a powerful and lightweight Inversion of Control (IoC) container for Go applications with comprehensive dependency injection capabilities.
Example ¶
Example demonstrates basic usage of the IoC container
package main
import (
"context"
"fmt"
"go-slim.dev/ioc"
)
func main() {
// Create services
type Database struct {
Name string
}
type UserService struct {
DB *Database
}
// Bind instances to the container
ioc.Bind(&Database{Name: "postgres"})
ioc.Bind(&UserService{})
// Retrieve instances
ctx := context.Background()
db, _ := ioc.Get[*Database](ctx)
fmt.Println((*db).Name)
}
Output: postgres
Index ¶
- Variables
- func Bind(instance any)
- func Call[T any](ctx context.Context, fn any) (T, error)
- func Call2[T any](ctx context.Context, fn any) (T, error)
- func Factory(factory any, shared ...bool) error
- func Get[T any](ctx context.Context) (*T, error)
- func GetFrom[T any](c *Container) (*T, error)
- func Invoke(ctx context.Context, fn any) ([]reflect.Value, error)
- func NamedBind(name string, instance any)
- func NamedFactory(name string, factory any, shared ...bool) error
- func NamedGet[T any](ctx context.Context, name string) (*T, error)
- func NamedGetFrom[T any](c *Container, name string) (*T, error)
- func NewContext(parentCtx context.Context) context.Context
- func Resolve(i any) error
- type Container
- func (c *Container) Bind(value any)
- func (c *Container) Factory(factory any, shared ...bool) error
- func (c *Container) Get(t reflect.Type) (reflect.Value, error)
- func (c *Container) Invoke(fn any) ([]reflect.Value, error)
- func (c *Container) NamedBind(name string, value any)
- func (c *Container) NamedFactory(name string, factory any, shared ...bool) error
- func (c *Container) NamedGet(name string, t reflect.Type) (reflect.Value, error)
- func (c *Container) NewChild() *Container
- func (c *Container) NewContext(parentCtx context.Context) context.Context
- func (c *Container) Resolve(i any) error
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrValueNotFound is returned when a requested value cannot be found in the container ErrValueNotFound = errors.New("ioc: value not found") // ErrFactoryFailed is returned when a factory function fails to create an instance ErrFactoryFailed = errors.New("ioc: factory function failed") // ErrInvalidType is returned when an invalid type is requested ErrInvalidType = errors.New("ioc: invalid type") // ErrDependencyResolution is returned when dependency resolution fails ErrDependencyResolution = errors.New("ioc: dependency resolution failed") )
Functions ¶
func Bind ¶
func Bind(instance any)
Bind binds a value to the container. Valid types include:
- Concrete implementation values of interfaces
- Struct instances
- Type values (avoid using primitive types, prefer using element type variants)
func Call ¶
Call executes the given function with automatic dependency injection and returns a single typed result. The function must return exactly one value of type T. This is a convenience wrapper around Invoke for functions with a single return value.
Example:
result, err := ioc.Call[string](ctx, func(db *Database) string {
return db.Query()
})
Example ¶
ExampleCall demonstrates function invocation with dependency injection
package main
import (
"context"
"fmt"
"go-slim.dev/ioc"
)
func main() {
type Greeter struct {
Prefix string
}
ioc.Bind(&Greeter{Prefix: "Hello"})
// Call function with automatic dependency injection
greeting, _ := ioc.Call[string](context.Background(), func(g *Greeter) string {
return g.Prefix + ", World!"
})
fmt.Println(greeting)
}
Output: Hello, World!
func Call2 ¶
Call2 executes the given function with automatic dependency injection and returns a typed result with error. The function must return exactly two values: a result of type T and an error. This is a convenience wrapper around Invoke for functions following the common (T, error) pattern.
Example:
user, err := ioc.Call2[*User](ctx, func(repo *UserRepository) (*User, error) {
return repo.FindByID(123)
})
if err != nil {
return err
}
Example ¶
ExampleCall2 demonstrates function invocation with error handling
package main
import (
"context"
"fmt"
"go-slim.dev/ioc"
)
func main() {
type Validator struct {
Enabled bool
}
ioc.Bind(&Validator{Enabled: true})
// Call function that returns (result, error)
result, err := ioc.Call2[string](context.Background(), func(v *Validator) (string, error) {
if v.Enabled {
return "Valid", nil
}
return "", fmt.Errorf("validation disabled")
})
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println(result)
}
Output: Valid
func Factory ¶
Factory binds a factory function to the container. Factory functions will be called lazily when the type is requested. The shared parameter determines if the factory result should be cached (singleton).
Example ¶
ExampleFactory demonstrates how to use factory functions
package main
import (
"context"
"fmt"
"go-slim.dev/ioc"
)
func main() {
type Config struct {
Port int
}
// Register a factory function
ioc.Factory(func() *Config {
return &Config{Port: 8080}
})
// Retrieve the instance
ctx := context.Background()
config, _ := ioc.Get[*Config](ctx)
fmt.Println((*config).Port)
}
Output: 8080
func Get ¶
Get retrieves a value of the specified type from the container. Generic type T should be a struct pointer. For interface instances, use the Instance function to get a Container instance, then use that container to get the concrete implementation of the interface.
func GetFrom ¶
GetFrom retrieves a value of the specified type from the given container. Generic type T should be a struct pointer. For interface instances, use the Container methods directly to get the concrete implementation. This function provides direct access to a specific container without context.
func Invoke ¶
Invoke executes the given function with automatic dependency injection. Function parameters are resolved from the container and injected automatically. It first checks the context for a container, then falls back to the default container.
func NamedBind ¶
NamedBind binds a named value to the container. Named bindings allow multiple implementations of the same type with different names.
Example ¶
ExampleNamedBind demonstrates multiple implementations with names
package main
import (
"context"
"fmt"
"go-slim.dev/ioc"
)
func main() {
type Logger struct {
Level string
}
// Bind multiple loggers with different names
ioc.NamedBind("debug", &Logger{Level: "DEBUG"})
ioc.NamedBind("info", &Logger{Level: "INFO"})
ctx := context.Background()
// Get specific logger by name
debugLogger, _ := ioc.NamedGet[*Logger](ctx, "debug")
infoLogger, _ := ioc.NamedGet[*Logger](ctx, "info")
fmt.Println((*debugLogger).Level)
fmt.Println((*infoLogger).Level)
}
Output: DEBUG INFO
func NamedFactory ¶
NamedFactory binds a named factory function to the container. Named factories allow multiple factory functions for the same type with different names.
func NamedGet ¶
NamedGet retrieves a value of the specified type with the given name from the container. It first checks the context for a container, then falls back to the defaultContainer container.
func NamedGetFrom ¶
NamedGetFrom retrieves a value of the specified type with the given name from the given container. This function provides direct access to a specific container without context. The container parameter specifies which container to retrieve the value from.
func NewContext ¶
NewContext creates a new context with the defaultContainer container embedded. The returned context can be used to retrieve the container via FromContext. The parentCtx must not be nil. Use context.Background() or context.TODO() if you don't have a context.
Example ¶
ExampleNewContext demonstrates passing container through context
package main
import (
"context"
"fmt"
"go-slim.dev/ioc"
)
func main() {
type RequestID struct {
ID string
}
// Create a request-specific container
requestContainer := ioc.New()
requestContainer.Bind(&RequestID{ID: "req-123"})
// Create context with container
ctx := requestContainer.NewContext(context.Background())
// Retrieve from context
reqID, _ := ioc.Get[*RequestID](ctx)
fmt.Println((*reqID).ID)
}
Output: req-123
func Resolve ¶
Resolve performs dependency injection on the given value. The value must be a pointer to a struct. Struct fields can be annotated with the "ioc" tag to specify which dependencies should be injected.
Example ¶
ExampleResolve demonstrates struct field injection with tags
package main
import (
"fmt"
"go-slim.dev/ioc"
)
func main() {
type Logger struct {
Name string
}
type Service struct {
Logger *Logger `ioc:"myLogger"`
}
// Bind with name
ioc.NamedBind("myLogger", &Logger{Name: "app-logger"})
// Create struct and resolve dependencies
svc := &Service{}
ioc.Resolve(svc)
fmt.Println(svc.Logger.Name)
}
Output: app-logger
Types ¶
type Container ¶
type Container struct {
// contains filtered or unexported fields
}
Container represents a service container that manages dependencies and their lifecycle. Thread-safe implementation using separate RWMutex for different data structures.
func Default ¶
func Default() *Container
Default returns the default container instance. This is useful for testing or when you need direct access to the default container.
func FromContext ¶
FromContext retrieves the container from the given context. Returns the container and true if found in context, or nil and false if not found. Note: Passing nil context will cause a panic; use context.TODO() if unsure.
func NewChild ¶
func NewChild() *Container
NewChild creates a child container from the default container. The child container starts empty but can access parent's services. Services registered in the child won't affect the default parent container.
Example ¶
ExampleNewChild demonstrates hierarchical containers
package main
import (
"context"
"fmt"
"go-slim.dev/ioc"
)
func main() {
type Config struct {
Env string
}
// Bind to default container
ioc.Bind(&Config{Env: "production"})
// Create child container with different config
child := ioc.NewChild()
child.Bind(&Config{Env: "testing"})
ctx := context.Background()
// Default container returns production config
prodConfig, _ := ioc.Get[*Config](ctx)
fmt.Println((*prodConfig).Env)
// Child container returns testing config
testConfig, _ := ioc.GetFrom[*Config](child)
fmt.Println((*testConfig).Env)
}
Output: production testing
func (*Container) Bind ¶
Bind binds a concrete implementation (instance or primitive value) to the container. Note: Since the internal mapping is established directly between type and concrete implementation, each type will have at most one concrete implementation.
func (*Container) Factory ¶
Factory binds a factory function to the container. The factory function must return a concrete implementation and can optionally return an error to indicate construction failure. Similar to the Bind method, each type will have at most one factory function.
func (*Container) Get ¶
Get retrieves a concrete implementation value of the specified type from the container. Retrieval steps:
- Check cached instances with exact type match
- Check factory functions with exact type match
- Check type-compatible instances (implements interface or assignable)
- Check type-compatible factories (implements interface or assignable)
- Check parent container if available
- If all above fail and type is a struct or struct pointer, auto-create an instance
func (*Container) Invoke ¶
Invoke executes the specified function with automatic dependency injection from the service container.
func (*Container) NamedBind ¶
NamedBind binds a named concrete implementation (instance or primitive value) to the container. Since this concrete implementation has a name, it won't override the concrete implementation bound through the Bind method. This allows specifying different concrete implementations for the same type in different scenarios and use cases. Structs can select the bound concrete implementation through the `ioc` tag during dependency injection.
func (*Container) NamedFactory ¶
NamedFactory binds a named factory function to the container. The implementation approach is similar to the NamedBind method.
func (*Container) NamedGet ¶
NamedGet retrieves a named concrete implementation value of the specified type from the container. Works exactly like Get but matches bindings by both name and type. The retrieval follows the same priority order as Get, checking instances, factories, parent container, and auto-creation for struct types.
func (*Container) NewChild ¶
NewChild creates a child container with this container as its parent. The child container:
- Starts empty (no bindings copied)
- Can access parent's services through lookup chain
- Can override parent's services without affecting parent
- Useful for creating scoped containers (e.g., per-request)
func (*Container) NewContext ¶
NewContext creates a new context with the container embedded. The returned context can be used to retrieve the container via FromContext. The parentCtx must not be nil. Use context.Background() or context.TODO() if you don't have a context.