Skip to content

junioryono/godi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

232 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

godi

Go Reference Go Report Card Build Status Coverage License: MIT

Dependency injection for Go with service lifetimes. godi automatically wires your application, manages service lifetimes, and handles cleanup - so you can focus on business logic.

services := godi.NewCollection() services.AddSingleton(NewDatabase) // One instance, shared everywhere services.AddScoped(NewUserService) // One instance per request services.AddTransient(NewEmailBuilder) // New instance every time provider, _ := services.Build() user := godi.MustResolve[*UserService](provider)

Why godi?

The problem: As applications grow, manually wiring dependencies becomes painful. Constructor parameters multiply, initialization order matters, and per-request isolation requires careful scope management.

// Manual wiring - gets messy fast config := NewConfig() logger := NewLogger(config) db := NewDatabase(config, logger) cache := NewCache(config, logger) userRepo := NewUserRepository(db, cache, logger) orderRepo := NewOrderRepository(db, cache, logger) userService := NewUserService(userRepo, logger) orderService := NewOrderService(orderRepo, userService, logger) // ... 20 more lines

The solution: Register constructors. godi figures out the rest.

// godi - register in any order, resolve anything services := godi.NewCollection() services.AddSingleton(NewConfig) services.AddSingleton(NewLogger) services.AddSingleton(NewDatabase) services.AddScoped(NewUserService) // ... godi handles the wiring

Installation

go get github.com/junioryono/godi/v4

Requires Go 1.21+. Zero external dependencies.

Quick Start

package main import ( "fmt" "github.com/junioryono/godi/v4" ) type Logger struct{} func (l *Logger) Log(msg string) { fmt.Println(msg) } func NewLogger() *Logger { return &Logger{} } type UserService struct { logger *Logger } func NewUserService(logger *Logger) *UserService { return &UserService{logger: logger} } func (s *UserService) Greet() { s.logger.Log("Hello from UserService!") } func main() { // 1. Register services services := godi.NewCollection() services.AddSingleton(NewLogger) services.AddSingleton(NewUserService) // 2. Build the container provider, _ := services.Build() defer provider.Close() // 3. Resolve and use - dependencies wired automatically users := godi.MustResolve[*UserService](provider) users.Greet() // Hello from UserService! }

Service Lifetimes

godi provides three lifetimes to control when instances are created:

┌──────────────────────────────────────────────────────────────────┐ │ Application │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ SINGLETON: Database, Logger, Config │ │ │ │ Created once, shared everywhere │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Request 1 │ │ Request 2 │ │ Request 3 │ │ │ │ SCOPED: │ │ SCOPED: │ │ SCOPED: │ │ │ │ Transaction │ │ Transaction │ │ Transaction │ │ │ │ UserSession │ │ UserSession │ │ UserSession │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └──────────────────────────────────────────────────────────────────┘ 
Lifetime Created Shared Use Case
Singleton Once App-wide Database pools, config
Scoped Once per scope Within scope Request context, transactions
Transient Every time Never Builders, temp objects
services.AddSingleton(NewDatabasePool) // One pool for the whole app services.AddScoped(NewTransaction) // Fresh transaction per request services.AddTransient(NewQueryBuilder) // New builder every resolution

HTTP Integration

godi shines in web applications where each request needs isolated services:

package main import ( "net/http" "github.com/junioryono/godi/v4" godihttp "github.com/junioryono/godi/v4/http" ) type UserController struct { // Dependencies injected automatically } func (c *UserController) List(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`["alice", "bob"]`)) } func main() { services := godi.NewCollection() services.AddSingleton(NewLogger) services.AddScoped(NewUserController) provider, _ := services.Build() defer provider.Close() mux := http.NewServeMux() mux.HandleFunc("GET /users", godihttp.Handle((*UserController).List)) // ScopeMiddleware creates a fresh scope per request handler := godihttp.ScopeMiddleware(provider)(mux) http.ListenAndServe(":8080", handler) }

Framework Support

Framework Package Install
net/http github.com/junioryono/godi/v4/http go get github.com/junioryono/godi/v4/http
Gin github.com/junioryono/godi/v4/gin go get github.com/junioryono/godi/v4/gin
Chi github.com/junioryono/godi/v4/chi go get github.com/junioryono/godi/v4/chi
Echo github.com/junioryono/godi/v4/echo go get github.com/junioryono/godi/v4/echo
Fiber github.com/junioryono/godi/v4/fiber go get github.com/junioryono/godi/v4/fiber

Features

Interface Binding

Register concrete types as interfaces for easy testing and swapping:

services.AddSingleton(NewConsoleLogger, godi.As[Logger]()) // Resolve by interface logger := godi.MustResolve[Logger](provider)

Keyed Services

Multiple implementations of the same type:

services.AddSingleton(NewPrimaryDB, godi.Name("primary")) services.AddSingleton(NewReplicaDB, godi.Name("replica")) primary := godi.MustResolveKeyed[Database](provider, "primary") replica := godi.MustResolveKeyed[Database](provider, "replica")

Service Groups

Collect related services for batch operations:

services.AddSingleton(NewEmailValidator, godi.Group("validators")) services.AddSingleton(NewPhoneValidator, godi.Group("validators")) validators := godi.MustResolveGroup[Validator](provider, "validators") for _, v := range validators { v.Validate(input) }

Parameter Objects

Simplify constructors with many dependencies:

type ServiceParams struct { godi.In DB Database Cache Cache Logger Logger Metrics Metrics `optional:"true"` } func NewService(params ServiceParams) *Service { return &Service{db: params.DB, cache: params.Cache} }

Result Objects

Register multiple services from one constructor:

type InfraResult struct { godi.Out DB *Database Cache *Cache Health *HealthChecker } func NewInfra(cfg *Config) InfraResult { db := connectDB(cfg) return InfraResult{ DB: db, Cache: NewCache(cfg), Health: NewHealthChecker(db), } } // One registration, three services services.AddSingleton(NewInfra)

Modules

Organize large applications:

// users/module.go var Module = godi.NewModule("users", godi.AddScoped(NewUserRepository), godi.AddScoped(NewUserService), ) // main.go services.AddModules( infrastructure.Module, users.Module, orders.Module, )

Automatic Cleanup

Services implementing Close() error are cleaned up automatically:

func (d *Database) Close() error { return d.conn.Close() } provider.Close() // Database.Close() called automatically

Error Handling

godi validates at build time to catch problems early:

provider, err := services.Build() if err != nil { // Circular dependency? Missing service? Lifetime conflict? // The error message tells you exactly what's wrong log.Fatal(err) }

Common errors caught at build time:

  • Circular dependencies - *A -> *B -> *A
  • Missing dependencies - *UserService requires *Database (not registered)
  • Lifetime conflicts - singleton *Cache cannot depend on scoped *RequestContext

Testing

Replace implementations for testing:

func TestUserService(t *testing.T) { services := godi.NewCollection() services.AddSingleton(func() Database { return &MockDB{} }) services.AddScoped(NewUserService) provider, _ := services.Build() defer provider.Close() scope, _ := provider.CreateScope(context.Background()) defer scope.Close() svc := godi.MustResolve[*UserService](scope) // Test with mock database }

Comparison

Feature godi Wire Fx do
No code generation Yes No Yes Yes
Service lifetimes Yes No No No
Scoped services Yes No No No
Build-time validation Yes Yes No No
HTTP framework integration Yes No No No
Parameter objects Yes No Yes No
Automatic cleanup Yes No Yes Yes

Performance

Benchmarks comparing godi with dig (Uber's DI, powers Fx) and do (samber's DI).

Run on Apple M2 Max. Source code.

Singleton Resolution

Library ns/op B/op allocs/op vs godi
godi 55 0 0 1x
do 180 192 6 3.3x
dig 640 736 20 12x

godi uses a lock-free cache with zero allocations. Singletons are created at build time, so every resolution is a fast cache lookup.

Concurrent Resolution

Library ns/op B/op allocs/op vs godi
godi 7 0 0 1x
do 297 224 6 42x
dig 366 736 20 52x

Under high concurrency, godi is 40-50x faster with zero contention.

Transient Resolution

Library ns/op B/op allocs/op
godi 176 40 2
do 188 208 7

New instance created on each call.

Cold Start

Library ns/op B/op allocs/op vs godi
godi 17,500 21,064 155 1x
dig 36,000 37,665 549 2.1x
do 47,000 30,511 351 2.7x

Full cycle: create container, register services, build, resolve. godi is 2x faster than dig.

Run benchmarks yourself
cd benchmarks && go test -bench=. -benchmem

Documentation

Full Documentation

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE for details.

About

Dependency Injection with Service Lifetimes for Go

Resources

License

Contributing

Stars

Watchers

Forks

Contributors

Languages