How to Use Modules

Modules are the best way to organize your godi services. Think of them as packages of related functionality.

Basic Module

Start simple - group related services:

// email/module.go
package email

import "github.com/junioryono/godi/v3"

var Module = godi.NewModule("email",
    godi.AddSingleton(NewSMTPClient),
    godi.AddScoped(NewEmailService),
    godi.AddScoped(NewEmailValidator),
)

Use it:

// main.go
services := godi.NewServiceCollection()
services.AddModules(email.Module)

provider, _ := services.BuildServiceProvider()

Module Dependencies

Modules can include other modules:

// core/module.go
var CoreModule = godi.NewModule("core",
    godi.AddSingleton(NewConfig),
    godi.AddSingleton(NewLogger),
)

// database/module.go
var DatabaseModule = godi.NewModule("database",
    CoreModule, // Depends on core!
    godi.AddSingleton(NewDatabaseConnection),
    godi.AddScoped(NewTransaction),
)

// user/module.go
var UserModule = godi.NewModule("user",
    DatabaseModule, // Depends on database (which includes core)
    godi.AddScoped(NewUserRepository),
    godi.AddScoped(NewUserService),
)

Real-World Examples

Example 1: Web API Module Structure

// infrastructure/logger/module.go
var LoggerModule = godi.NewModule("logger",
    godi.AddSingleton(NewLogConfig),
    godi.AddSingleton(NewLogger),
)

// infrastructure/database/module.go
var DatabaseModule = godi.NewModule("database",
    LoggerModule,
    godi.AddSingleton(NewDatabaseConfig),
    godi.AddSingleton(NewDatabaseConnection),
    godi.AddScoped(NewTransaction),
)

// infrastructure/cache/module.go
var CacheModule = godi.NewModule("cache",
    LoggerModule,
    godi.AddSingleton(NewRedisConfig),
    godi.AddSingleton(NewRedisClient),
)

// features/user/module.go
var UserModule = godi.NewModule("user",
    DatabaseModule,
    CacheModule,
    godi.AddScoped(NewUserRepository),
    godi.AddScoped(NewUserService),
    godi.AddScoped(NewUserHandler),
)

// features/auth/module.go
var AuthModule = godi.NewModule("auth",
    UserModule, // Auth depends on users
    godi.AddSingleton(NewJWTService),
    godi.AddScoped(NewAuthService),
    godi.AddScoped(NewAuthMiddleware),
)

// app/module.go
var AppModule = godi.NewModule("app",
    UserModule,
    AuthModule,
    // Add more feature modules as needed
)

Example 2: Environment-Specific Modules

// environments/development.go
var DevelopmentModule = godi.NewModule("dev",
    godi.AddSingleton(func() Database {
        return NewSQLiteDatabase(":memory:")
    }),
    godi.AddSingleton(func() Cache {
        return NewMemoryCache()
    }),
    godi.AddSingleton(func() EmailClient {
        return NewMockEmailClient()
    }),
)

// environments/production.go
var ProductionModule = godi.NewModule("prod",
    godi.AddSingleton(func() Database {
        return NewPostgresDatabase(os.Getenv("DATABASE_URL"))
    }),
    godi.AddSingleton(func() Cache {
        return NewRedisCache(os.Getenv("REDIS_URL"))
    }),
    godi.AddSingleton(func() EmailClient {
        return NewSendGridClient(os.Getenv("SENDGRID_KEY"))
    }),
)

// main.go
func main() {
    // Base module with business logic
    baseModule := godi.NewModule("base",
        UserModule,
        AuthModule,
        OrderModule,
    )

    // Choose environment
    var envModule godi.ModuleOption
    if os.Getenv("ENV") == "production" {
        envModule = ProductionModule
    } else {
        envModule = DevelopmentModule
    }

    // Combine modules
    appModule := godi.NewModule("app",
        envModule,
        baseModule,
    )

    // Build and run
    services := godi.NewServiceCollection()
    services.AddModules(appModule)
    provider, _ := services.BuildServiceProvider()
}

Example 3: Plugin System with Modules

// plugin/interface.go
type Plugin interface {
    Name() string
    Initialize() error
}

// plugins/analytics/module.go
var AnalyticsModule = godi.NewModule("analytics-plugin",
    godi.AddSingleton(NewAnalyticsClient),
    godi.AddSingleton(func(client *AnalyticsClient) Plugin {
        return &AnalyticsPlugin{client: client}
    }, godi.Group("plugins")),
)

// plugins/monitoring/module.go
var MonitoringModule = godi.NewModule("monitoring-plugin",
    godi.AddSingleton(NewMetricsCollector),
    godi.AddSingleton(func(collector *MetricsCollector) Plugin {
        return &MonitoringPlugin{collector: collector}
    }, godi.Group("plugins")),
)

// app/plugin_manager.go
type PluginManager struct {
    plugins []Plugin
}

func NewPluginManager(in struct {
    godi.In
    Plugins []Plugin `group:"plugins"`
}) *PluginManager {
    return &PluginManager{plugins: in.Plugins}
}

// main.go
var AppModule = godi.NewModule("app",
    CoreModule,
    AnalyticsModule,
    MonitoringModule,
    godi.AddSingleton(NewPluginManager),
)

Advanced Patterns

Dynamic Module Configuration

func DatabaseModule(config DatabaseConfig) godi.ModuleOption {
    return godi.NewModule("database",
        godi.AddSingleton(func() *DatabaseConfig {
            return &config
        }),
        godi.AddSingleton(NewDatabaseConnection),
    )
}

// Use with configuration
dbConfig := DatabaseConfig{
    Host:     "localhost",
    Port:     5432,
    Database: "myapp",
}

appModule := godi.NewModule("app",
    DatabaseModule(dbConfig),
    UserModule,
)

Conditional Module Registration

func AppModule(features FeatureFlags) godi.ModuleOption {
    modules := []godi.ModuleOption{
        CoreModule,
        DatabaseModule,
    }

    if features.UsersEnabled {
        modules = append(modules, UserModule)
    }

    if features.BillingEnabled {
        modules = append(modules, BillingModule)
    }

    if features.AnalyticsEnabled {
        modules = append(modules, AnalyticsModule)
    }

    return godi.NewModule("app", modules...)
}

Testing with Module Overrides

// Create test module that replaces services
var TestOverridesModule = godi.NewModule("test-overrides",
    godi.AddSingleton(func() Database {
        return &MockDatabase{
            users: []User{
                {ID: "1", Name: "Test User"},
            },
        }
    }),
    godi.AddSingleton(func() EmailClient {
        return &MockEmailClient{
            sentEmails: []Email{},
        }
    }),
)

func TestUserService(t *testing.T) {
    // Combine production module with test overrides
    testModule := godi.NewModule("test",
        UserModule,        // Production user logic
        TestOverridesModule, // Test implementations
    )

    services := godi.NewServiceCollection()
    services.AddModules(testModule)

    provider, _ := services.BuildServiceProvider()
    // Test with mocked dependencies
}

Best Practices

1. One Module Per Package

// ✅ Good: user/module.go
package user

var Module = godi.NewModule("user",
    godi.AddScoped(NewRepository),
    godi.AddScoped(NewService),
    godi.AddScoped(NewHandler),
)

2. Clear Module Dependencies

// ✅ Good: Explicit dependencies
var OrderModule = godi.NewModule("order",
    UserModule,     // Orders need users
    InventoryModule, // Orders need inventory
    PaymentModule,   // Orders need payment
    godi.AddScoped(NewOrderService),
)

3. Module Naming Conventions

// ✅ Good: Clear, consistent names
var UserModule = godi.NewModule("user", ...)
var AuthModule = godi.NewModule("auth", ...)
var CoreModule = godi.NewModule("core", ...)

// ❌ Bad: Inconsistent or unclear
var UsrMod = godi.NewModule("usr", ...)
var Authentication = godi.NewModule("authentication", ...)
var BasicStuff = godi.NewModule("basic", ...)

4. Document Module Purpose

// Package user provides user management functionality.
//
// The UserModule includes:
// - User repository for database operations
// - User service for business logic
// - User handler for HTTP endpoints
// - User validator for input validation
//
// Dependencies:
// - DatabaseModule (for database connection)
// - CacheModule (for caching user data)
var UserModule = godi.NewModule("user",
    DatabaseModule,
    CacheModule,
    godi.AddScoped(NewUserRepository),
    godi.AddScoped(NewUserService),
    godi.AddScoped(NewUserHandler),
    godi.AddScoped(NewUserValidator),
)

Common Patterns

Feature Toggle Module

type Features struct {
    NewCheckout bool
    BetaSearch  bool
}

func FeatureModule(features Features) godi.ModuleOption {
    return godi.NewModule("features",
        godi.AddSingleton(func() Features { return features }),
    )
}

// In services
func NewSearchService(features Features, /* other deps */) *SearchService {
    if features.BetaSearch {
        return &BetaSearchService{/* ... */}
    }
    return &SearchService{/* ... */}
}

Multi-Tenant Module

var TenantModule = godi.NewModule("tenant",
    godi.AddScoped(func(ctx context.Context) *TenantContext {
        tenantID := ctx.Value("tenantID").(string)
        return &TenantContext{TenantID: tenantID}
    }),
)

// Services automatically get tenant context
func NewUserRepository(db *Database, tenant *TenantContext) *UserRepository {
    return &UserRepository{
        db:       db,
        tenantID: tenant.TenantID,
    }
}

Summary

Modules are powerful because they:

  • Organize your code into logical units

  • Encapsulate dependencies

  • Enable reuse across projects

  • Simplify testing with easy overrides

  • Scale from simple apps to complex systems

Start with simple modules and add complexity as needed. The goal is clarity and maintainability!