A lightweight, high-performance dependency injection library for Go.
injector is a compile-time-safe dependency injection container designed for Go applications that need reliable, fast dependency resolution at startup. It provides multiple caching strategies to optimize for different access patterns and supports flexible lifecycle management for registered types.
- Type-Safe API: Generic functions for compile-time type safety
- Multiple Lifecycle Options: Singleton, Scope, and Transient lifecycles
- Error Handling: Constructors can optionally return an error value
- Flexible Registration: Register by type with custom constructors
- Named Lookups: Retrieve dependencies by type name
- Caching Strategies: Choose between Map, BubbleList, and PriorityList caches
- Dependency Verification: Validate your dependency graph before runtime
- Scoped Instances: Create isolated scopes for request-specific dependencies
- Function Injection: Automatically inject dependencies into functions
go get github.com/smarty/injectorpackage main
import (
"github.com/smarty/injector"
)
type Database interface {
Query(sql string) ([]string, error)
}
type MySQLDB struct{}
func (d *MySQLDB) Query(sql string) ([]string, error) {
// Implementation
return []string{}, nil
}
func main() {
di := injector.New()
// Register a singleton instance
injector.RegisterSingleton[Database](di, func() Database {
return &MySQLDB{}
})
// Verify the dependency graph
if err := injector.Verify(di); err != nil {
panic(err)
}
// Retrieve the database
db, err := injector.Get[Database](di)
if err != nil {
panic(err)
}
// Use the database
_ = db
}Instantiated once and reused across the entire application lifetime.
injector.RegisterSingleton[MyType](di, constructor)Instantiated once per Get() call, allowing for request-scoped or operation-scoped instances.
injector.RegisterScope[MyType](di, constructor)Instantiated every time it's requested as a dependency.
injector.RegisterTransient[MyType](di, constructor)Constructors can return an error in addition to the instance:
injector.RegisterSingletonError[MyType](di, func() (MyType, error) {
// Return instance and error
return MyType{}, nil
})Automatically inject dependencies into functions:
func setupDatabase(db Database) error {
// Setup logic
return nil
}
// Call the function with injected dependencies
err := injector.Call(di, setupDatabase)Register types that can be retrieved by name:
db, err := injector.GetByName(di, "Database")Choose the caching strategy that best fits your access patterns:
// Map: Good for random access patterns (default)
di := injector.New(injector.Map)
// BubbleList: Good for stable access patterns
di := injector.New(injector.BubbleList)
// PriorityList: Good for stable but changing access patterns
di := injector.New(injector.PriorityList)New(cacheStrategy ...CacheStrategy) *Injector: Create a new injector instanceGet[T](di *Injector) (T, error): Retrieve a dependency by typeGetByName(di *Injector, name string) (any, error): Retrieve a dependency by nameCall(di *Injector, function any) error: Call a function with injected dependenciesCall1throughCall4: Call functions with specific return value countsCallN(di *Injector, function any) ([]any, error): Call a function with any number of returnsVerify(di *Injector) error: Validate the dependency graph
RegisterSingleton[T](di *Injector, constructor any) error: Register a singletonRegisterSingletonError[T](di *Injector, constructor any) error: Register a singleton with error handlingRegisterScope[T](di *Injector, constructor any) error: Register a scoped instanceRegisterScopeError[T](di *Injector, constructor any) error: Register a scoped instance with error handlingRegisterTransient[T](di *Injector, constructor any) error: Register a transient instanceRegisterTransientError[T](di *Injector, constructor any) error: Register a transient with error handling
The injector provides specific error types for different failure scenarios:
ErrorAlreadyRegistered: A type has already been registeredErrorBadState: Injector is in an invalid state for the requested operationErrorDependencyLoop: A circular dependency has been detectedErrorNotRegistered: A required dependency has not been registeredErrorNotStructOrInterface: A type is not suitable for registrationErrorVariadicArguments: A function has a variadic signature
- The injector is optimized for startup-time usage. Generating dependencies takes a few microseconds per call.
- Choose a caching strategy based on your access patterns:
- Map: Random access, no reordering overhead
- BubbleList: Stable patterns, benefits from reordering on stable workloads
- PriorityList: Changing patterns that settle over time
The library includes comprehensive tests covering:
- Type registration and validation
- Dependency resolution
- Circular dependency detection
- Error handling
- Performance benchmarks
Run tests with:
go test ./...Run benchmarks with:
go test -bench=. ./...-
Struct Tag Support for Automatic Field Injection - Allow dependency injection directly into struct fields via tags (e.g.,
di:"inject"). -
Factory Pattern Support - Register factory functions that can take parameters to create multiple instances with different configurations. Useful for creating variants of the same type based on input parameters.
MIT License - See LICENSE for details