šŸ“š godi - Awesome Go Library for Miscellaneous

Go Gopher mascot for godi

Microsoft-style dependency injection for Go with scoped lifetimes and generics

šŸ·ļø Miscellaneous
šŸ“‚ Dependency Injection
⭐ 0 stars
View on GitHub šŸ”—

Detailed Description of godi

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 │            │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜            │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
LifetimeCreatedSharedUse Case
SingletonOnceApp-wideDatabase pools, config
ScopedOnce per scopeWithin scopeRequest context, transactions
TransientEvery timeNeverBuilders, 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

FrameworkPackageInstall
net/httpgithub.com/junioryono/godi/v4/httpgo get github.com/junioryono/godi/v4/http
Gingithub.com/junioryono/godi/v4/gingo get github.com/junioryono/godi/v4/gin
Chigithub.com/junioryono/godi/v4/chigo get github.com/junioryono/godi/v4/chi
Echogithub.com/junioryono/godi/v4/echogo get github.com/junioryono/godi/v4/echo
Fibergithub.com/junioryono/godi/v4/fibergo 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

FeaturegodiWireFxdo
No code generationYesNoYesYes
Service lifetimesYesNoNoNo
Scoped servicesYesNoNoNo
Build-time validationYesYesNoNo
HTTP framework integrationYesNoNoNo
Parameter objectsYesNoYesNo
Automatic cleanupYesNoYesYes

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

Libraryns/opB/opallocs/opvs godi
godi55001x
do18019263.3x
dig6407362012x

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

Libraryns/opB/opallocs/opvs godi
godi7001x
do297224642x
dig3667362052x

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

Transient Resolution

Libraryns/opB/opallocs/op
godi176402
do1882087

New instance created on each call.

Cold Start

Libraryns/opB/opallocs/opvs godi
godi17,50021,0641551x
dig36,00037,6655492.1x
do47,00030,5113512.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.