How godi Works
godi is a dependency injection container that automatically resolves and creates your services. Here’s the mental model.
The Big Picture
┌─────────────────────────────────────────────────────────────────┐
│ Your Code │
├─────────────────────────────────────────────────────────────────┤
│ │
│ type Logger struct{} │
│ type Database struct { logger *Logger } │
│ type UserService struct { db *Database, logger *Logger } │
│ │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Service Collection │
│ "Here's what I have" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ AddSingleton(NewLogger) │
│ AddSingleton(NewDatabase) │
│ AddScoped(NewUserService) │
│ │
└──────────────────────────────┬──────────────────────────────────┘
│ Build()
▼
┌─────────────────────────────────────────────────────────────────┐
│ Dependency Graph │
│ "Here's how things connect" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ UserService ──────┬──────▶ Database ──────▶ Logger │
│ │ │
│ └──────────────────────▶ Logger │
│ │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Provider │
│ "Ask me for anything" │
├─────────────────────────────────────────────────────────────────┤
│ │
│ godi.MustResolve[*UserService](provider) │
│ │
│ 1. Check if Logger exists → No → Create Logger │
│ 2. Check if Database exists → No → Create Database(Logger) │
│ 3. Create UserService(Database, Logger) │
│ 4. Return UserService │
│ │
└─────────────────────────────────────────────────────────────────┘
Step by Step
1. You Write Constructors
Normal Go functions that create your types:
func NewLogger() *Logger {
return &Logger{}
}
func NewDatabase(logger *Logger) *Database {
return &Database{logger: logger}
}
func NewUserService(db *Database, logger *Logger) *UserService {
return &UserService{db: db, logger: logger}
}
godi reads the function signatures to understand dependencies.
2. You Register Services
Tell godi about your constructors:
services := godi.NewCollection()
services.AddSingleton(NewLogger)
services.AddSingleton(NewDatabase)
services.AddScoped(NewUserService)
Registration order doesn’t matter. godi will figure out the correct creation order.
3. godi Builds the Graph
When you call Build(), godi:
Analyzes constructors - Looks at function parameters and return types
Builds dependency graph - Maps what depends on what
Validates - Checks for circular dependencies, missing services, lifetime conflicts
Returns provider - Ready to create services on demand
provider, err := services.Build()
if err != nil {
// Something's wrong with your registrations
log.Fatal(err)
}
4. You Request Services
When you resolve a service, godi walks the dependency graph:
userService := godi.MustResolve[*UserService](provider)
godi creates dependencies in order:
Logger first (no dependencies)
Database next (needs Logger)
UserService last (needs Database and Logger)
Type Resolution
godi uses Go generics for type-safe resolution:
// The type in brackets must match what you registered
logger := godi.MustResolve[*Logger](provider)
db := godi.MustResolve[*Database](provider)
users := godi.MustResolve[*UserService](provider)
If you request a type that wasn’t registered, you get an error:
// Error: no registration found for type *NotRegistered
thing := godi.MustResolve[*NotRegistered](provider)
Instance Caching
godi caches instances based on lifetime:
// Singleton: same instance every time
logger1 := godi.MustResolve[*Logger](provider)
logger2 := godi.MustResolve[*Logger](provider)
// logger1 == logger2 ✓
// Transient: new instance every time
services.AddTransient(NewTempFile)
file1 := godi.MustResolve[*TempFile](provider)
file2 := godi.MustResolve[*TempFile](provider)
// file1 == file2 ✗
Error Handling
godi validates at build time to catch problems early:
Circular Dependencies
// A needs B, B needs A
services.AddSingleton(func(b *B) *A { return &A{} })
services.AddSingleton(func(a *A) *B { return &B{} })
provider, err := services.Build()
// Error: circular dependency detected: *A -> *B -> *A
Missing Dependencies
services.AddSingleton(func(missing *NotRegistered) *MyService {
return &MyService{}
})
provider, err := services.Build()
// Error: no registration found for type *NotRegistered required by *MyService
Lifetime Conflicts
services.AddScoped(NewRequestContext)
services.AddSingleton(func(ctx *RequestContext) *Cache {
return &Cache{}
})
provider, err := services.Build()
// Error: singleton *Cache cannot depend on scoped *RequestContext
Cleanup
Services implementing Close() error are automatically cleaned up:
type Database struct {
conn *sql.DB
}
func (d *Database) Close() error {
return d.conn.Close()
}
// When you close the provider, Database.Close() is called
provider.Close()
Next: Learn about service lifetimes