A simple and lightweight dependency injection container for Go.
- Simple API: Easy to use with minimal boilerplate
- Factory Functions: Register factory functions for lazy initialization
- Direct Instances: Register pre-created instances
- Singleton Pattern: Factory functions are called only once, instances are reused
- Error Handling: Proper error handling with optional panic mode
- Zero Dependencies: No external dependencies, uses only Go standard library
- Thread-Safe: Safe for concurrent use (coming soon)
go get github.com/Javlopez/injectorpackage main
import (
"fmt"
"github.com/Javlopez/injector"
)
type Database struct {
Name string
}
func NewDB() *Database {
return &Database{Name: "production-db"}
}
func main() {
// Create injector
inj := injector.NewInjector()
// Register factory function
inj.Inject(NewDB, "database")
// Resolve dependency
db := inj.MustResolve("database").(*Database)
fmt.Println(db.Name) // Output: production-db
}injector := injector.NewInjector()Factory functions are called lazily when the dependency is first resolved:
func NewDatabase() *Database {
return &Database{
ConnectionString: "postgres://localhost:5432/mydb",
MaxConnections: 10,
}
}
// Register factory
injector.Inject(NewDatabase, "database")Pre-created instances are stored and returned as-is:
db := &Database{
ConnectionString: "postgres://localhost:5432/mydb",
MaxConnections: 10,
}
// Register instance
injector.Inject(db, "database")dep, err := injector.Resolve("database")
if err != nil {
log.Fatal(err)
}
db := dep.(*Database)db := injector.MustResolve("database").(*Database)You can register dependencies that depend on other dependencies:
type UserService struct {
DB *Database
Logger *Logger
}
func NewUserService(db *Database, logger *Logger) *UserService {
return &UserService{
DB: db,
Logger: logger,
}
}
// Register dependencies
injector.Inject(NewDatabase, "database")
injector.Inject(NewLogger, "logger")
// Register service that depends on other services
injector.Inject(func() *UserService {
db := injector.MustResolve("database").(*Database)
logger := injector.MustResolve("logger").(*Logger)
return NewUserService(db, logger)
}, "userService")
// Resolve the complex service
userSvc := injector.MustResolve("userService").(*UserService)package main
import (
"fmt"
"log"
"github.com/Javlopez/injector"
)
// Domain types
type Database struct {
ConnectionString string
MaxConnections int
}
type Logger struct {
Level string
}
type UserService struct {
DB *Database
Logger *Logger
}
type UserController struct {
UserService *UserService
Logger *Logger
}
// Factory functions
func NewDatabase() *Database {
return &Database{
ConnectionString: "postgres://localhost:5432/myapp",
MaxConnections: 25,
}
}
func NewLogger() *Logger {
return &Logger{Level: "info"}
}
func NewUserService(db *Database, logger *Logger) *UserService {
return &UserService{DB: db, Logger: logger}
}
func NewUserController(userSvc *UserService, logger *Logger) *UserController {
return &UserController{UserService: userSvc, Logger: logger}
}
func main() {
// Create injector
inj := injector.NewInjector()
// Register base dependencies
inj.Inject(NewDatabase, "database")
inj.Inject(NewLogger, "logger")
// Register service layer
inj.Inject(func() *UserService {
db := inj.MustResolve("database").(*Database)
logger := inj.MustResolve("logger").(*Logger)
return NewUserService(db, logger)
}, "userService")
// Register controller layer
inj.Inject(func() *UserController {
userSvc := inj.MustResolve("userService").(*UserService)
logger := inj.MustResolve("logger").(*Logger)
return NewUserController(userSvc, logger)
}, "userController")
// Resolve and use
controller := inj.MustResolve("userController").(*UserController)
fmt.Printf("Database: %s\n", controller.UserService.DB.ConnectionString)
fmt.Printf("Logger Level: %s\n", controller.Logger.Level)
}// Good: Factory function handles complex initialization
func NewDatabaseConnection() *Database {
config := loadConfig()
db, err := sql.Open("postgres", config.ConnectionString)
if err != nil {
log.Fatal(err)
}
return &Database{conn: db}
}
injector.Inject(NewDatabaseConnection, "database")// Infrastructure layer
injector.Inject(NewDatabase, "database")
injector.Inject(NewRedisClient, "redis")
injector.Inject(NewLogger, "logger")
// Service layer
injector.Inject(NewUserService, "userService")
injector.Inject(NewOrderService, "orderService")
// Controller layer
injector.Inject(NewUserController, "userController")
injector.Inject(NewOrderController, "orderController")// Good
injector.Inject(NewPostgresDatabase, "postgresDatabase")
injector.Inject(NewRedisCache, "redisCache")
// Avoid
injector.Inject(NewDB, "db")
injector.Inject(NewCache, "cache")// In application startup (use MustResolve)
db := injector.MustResolve("database").(*Database)
// In request handlers (use Resolve)
dep, err := injector.Resolve("optionalService")
if err != nil {
// Handle gracefully
log.Printf("Optional service not available: %v", err)
}The main dependency injection container.
Creates a new injector instance.
Registers a dependency with the given name. The dependency can be:
- A factory function that returns an instance
- A pre-created instance
Resolves a dependency by name. Returns the instance and an error if not found.
Resolves a dependency by name. Panics if the dependency is not found.
The package includes comprehensive tests. Run them with:
go test -v
go test -race
go test -coverFor benchmarks:
go test -bench=.- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Thread-safety improvements
- Circular dependency detection
- Auto-wiring by type
- Lifecycle management (init/destroy hooks)
- Configuration from files (JSON/YAML)
- Performance optimizations
Q: Is this thread-safe? A: Currently, no. Thread-safety is planned for a future release. For now, register all dependencies at application startup before concurrent access.
Q: How does this compare to other DI containers? A: This injector focuses on simplicity and minimal overhead. It's perfect for small to medium applications that need basic dependency injection without complex features.
Q: Can I register the same dependency with different names? A: Yes! You can register the same factory function or instance with multiple names.
Q: What happens if I register a dependency twice with the same name? A: The second registration will override the first one.