Adding Services
Real applications have services that depend on each other. godi automatically wires these dependencies together.
The Magic: Automatic Wiring
Write your constructors normally. godi figures out what to pass in.
// Logger has no dependencies
type Logger struct{}
func NewLogger() *Logger {
return &Logger{}
}
// UserService depends on Logger
type UserService struct {
logger *Logger
}
func NewUserService(logger *Logger) *UserService {
return &UserService{logger: logger}
}
Register both:
services := godi.NewCollection()
services.AddSingleton(NewLogger)
services.AddSingleton(NewUserService)
Resolve:
users := godi.MustResolve[*UserService](provider)
// users.logger is already set!
godi saw that NewUserService needs a *Logger, found NewLogger, and called it first.
How It Works
┌────────────────────────────────────────────────────────┐
│ You register: │
│ NewLogger() → *Logger │
│ NewUserService() → *UserService (needs *Logger) │
├────────────────────────────────────────────────────────┤
│ godi builds dependency graph: │
│ │
│ *UserService │
│ │ │
│ └──depends on──▶ *Logger │
├────────────────────────────────────────────────────────┤
│ When you resolve *UserService: │
│ 1. Create *Logger (no deps) │
│ 2. Create *UserService (pass *Logger) │
│ 3. Return *UserService │
└────────────────────────────────────────────────────────┘
A Realistic Example
package main
import (
"fmt"
"log"
"github.com/junioryono/godi/v4"
)
// Logger - no dependencies
type Logger struct {
prefix string
}
func NewLogger() *Logger {
return &Logger{prefix: "[APP]"}
}
func (l *Logger) Log(msg string) {
fmt.Printf("%s %s\n", l.prefix, msg)
}
// Config - no dependencies
type Config struct {
DatabaseURL string
Debug bool
}
func NewConfig() *Config {
return &Config{
DatabaseURL: "postgres://localhost/myapp",
Debug: true,
}
}
// Database - depends on Config and Logger
type Database struct {
config *Config
logger *Logger
}
func NewDatabase(config *Config, logger *Logger) *Database {
logger.Log("Connecting to database...")
return &Database{config: config, logger: logger}
}
func (d *Database) Query(sql string) {
d.logger.Log("Executing: " + sql)
}
// UserService - depends on Database and Logger
type UserService struct {
db *Database
logger *Logger
}
func NewUserService(db *Database, logger *Logger) *UserService {
return &UserService{db: db, logger: logger}
}
func (u *UserService) GetUser(id int) {
u.logger.Log(fmt.Sprintf("Getting user %d", id))
u.db.Query(fmt.Sprintf("SELECT * FROM users WHERE id = %d", id))
}
func main() {
services := godi.NewCollection()
// Register in any order - godi figures out the dependency order
services.AddSingleton(NewUserService)
services.AddSingleton(NewDatabase)
services.AddSingleton(NewLogger)
services.AddSingleton(NewConfig)
provider, err := services.Build()
if err != nil {
log.Fatal(err)
}
defer provider.Close()
// Everything is wired up automatically
users := godi.MustResolve[*UserService](provider)
users.GetUser(42)
}
Output:
[APP] Connecting to database...
[APP] Getting user 42
[APP] Executing: SELECT * FROM users WHERE id = 42
Constructor Patterns
godi supports several constructor patterns:
// Simple constructor
func NewLogger() *Logger
// With dependencies
func NewUserService(logger *Logger, db *Database) *UserService
// With error return
func NewDatabase(config *Config) (*Database, error)
// Anonymous function
services.AddSingleton(func(logger *Logger) *Cache {
return &Cache{logger: logger}
})
Interface Registration
Register a concrete type to satisfy an interface:
type Logger interface {
Log(string)
}
type consoleLogger struct{}
func (c *consoleLogger) Log(msg string) { fmt.Println(msg) }
// Register concrete type as interface
services.AddSingleton(func() *consoleLogger {
return &consoleLogger{}
}, godi.As[Logger]())
// Resolve by interface
logger := godi.MustResolve[Logger](provider)
Key Points
Write normal Go constructors - godi handles the wiring
Registration order doesn’t matter
Dependencies are resolved recursively
Errors during construction are returned from
Build()orResolve()