Modules
Organize your services into logical groups for better maintainability and reusability.
What are Modules?
Modules are a way to group related service registrations together. They help you:
Organize large applications
Share service configurations
Enable/disable features
Improve code reusability
// Define a module
var DatabaseModule = godi.NewModule("database",
godi.AddSingleton(NewDatabaseConfig),
godi.AddSingleton(NewConnectionPool),
godi.AddScoped(NewTransaction),
)
// Use the module
services := godi.NewCollection()
services.AddModules(DatabaseModule)
Creating Modules
Basic Module
package auth
import "github.com/junioryono/godi/v4"
var Module = godi.NewModule("auth",
godi.AddSingleton(NewPasswordHasher),
godi.AddSingleton(NewJWTService),
godi.AddScoped(NewAuthService),
godi.AddScoped(NewUserValidator),
)
Module with Configuration
package database
type Options struct {
Host string
Port int
Database string
MaxConns int
}
func NewModule(opts Options) godi.ModuleOption {
return godi.NewModule("database",
// Register config
godi.AddSingleton(func() *Config {
return &Config{
Host: opts.Host,
Port: opts.Port,
Database: opts.Database,
MaxConns: opts.MaxConns,
}
}),
// Register services
godi.AddSingleton(NewConnectionPool),
godi.AddScoped(NewQueryBuilder),
)
}
// Usage
services.AddModules(database.NewModule(database.Options{
Host: "localhost",
Port: 5432,
Database: "myapp",
MaxConns: 25,
}))
Module Organization
Layer-Based Structure
app/
├── infrastructure/
│ ├── module.go
│ ├── config.go
│ ├── database.go
│ └── cache.go
├── repositories/
│ ├── module.go
│ ├── user_repository.go
│ └── order_repository.go
├── services/
│ ├── module.go
│ ├── user_service.go
│ └── order_service.go
└── main.go
Infrastructure Module:
package infrastructure
var Module = godi.NewModule("infrastructure",
godi.AddSingleton(NewConfig),
godi.AddSingleton(NewLogger),
godi.AddSingleton(NewDatabase),
godi.AddSingleton(NewCache),
)
Repository Module:
package repositories
var Module = godi.NewModule("repositories",
godi.AddScoped(NewUserRepository),
godi.AddScoped(NewOrderRepository),
godi.AddScoped(NewProductRepository),
)
Service Module:
package services
var Module = godi.NewModule("services",
godi.AddScoped(NewUserService),
godi.AddScoped(NewOrderService),
godi.AddScoped(NewEmailService),
)
Main Application:
package main
func main() {
services := godi.NewCollection()
// Add all modules
services.AddModules(
infrastructure.Module,
repositories.Module,
services.Module,
)
provider, _ := services.Build()
defer provider.Close()
// Start application
}
Feature-Based Structure
app/
├── users/
│ ├── module.go
│ ├── user.go
│ ├── repository.go
│ └── service.go
├── orders/
│ ├── module.go
│ ├── order.go
│ ├── repository.go
│ └── service.go
└── main.go
User Module:
package users
var Module = godi.NewModule("users",
godi.AddScoped(NewUserRepository),
godi.AddScoped(NewUserService),
godi.AddScoped(NewUserValidator),
)
Order Module:
package orders
var Module = godi.NewModule("orders",
godi.AddScoped(NewOrderRepository),
godi.AddScoped(NewOrderService),
godi.AddScoped(NewOrderProcessor),
)
Composing Modules
Nested Modules
// Core module contains shared services
var CoreModule = godi.NewModule("core",
godi.AddSingleton(NewLogger),
godi.AddSingleton(NewConfig),
godi.AddSingleton(NewMetrics),
)
// Database module
var DatabaseModule = godi.NewModule("database",
godi.AddSingleton(NewConnectionPool),
godi.AddScoped(NewTransaction),
)
// Application module combines others
var AppModule = godi.NewModule("app",
CoreModule, // Include core
DatabaseModule, // Include database
godi.AddScoped(NewAppService),
)
Conditional Modules
func GetModules(config *Config) []godi.ModuleOption {
modules := []godi.ModuleOption{
CoreModule,
DatabaseModule,
}
// Add cache if enabled
if config.CacheEnabled {
modules = append(modules, CacheModule)
}
// Add auth based on type
switch config.AuthType {
case "oauth":
modules = append(modules, OAuthModule)
case "basic":
modules = append(modules, BasicAuthModule)
}
// Feature flags
if config.Features.BetaAPI {
modules = append(modules, BetaAPIModule)
}
return modules
}
// Usage
services.AddModules(GetModules(config)...)
Module Dependencies
Explicit Dependencies
// Shared module - no dependencies
var SharedModule = godi.NewModule("shared",
godi.AddSingleton(NewLogger),
godi.AddSingleton(NewConfig),
)
// Database module - depends on shared
var DatabaseModule = godi.NewModule("database",
godi.AddSingleton(NewDatabase), // Needs Logger from shared
)
// App module - depends on both
var AppModule = godi.NewModule("app",
godi.AddScoped(NewAppService), // Needs Database and Logger
)
// Register in order
services.AddModules(
SharedModule, // First
DatabaseModule, // Second
AppModule, // Third
)
Testing with Modules
Test Module
// Production module
var ProductionModule = godi.NewModule("production",
godi.AddSingleton(NewPostgresDB, godi.As[Database]()),
godi.AddSingleton(NewRedisCache, godi.As[Cache]()),
)
// Test module with mocks
var TestModule = godi.NewModule("test",
godi.AddSingleton(NewMockDB, godi.As[Database]()),
godi.AddSingleton(NewMemoryCache, godi.As[Cache]()),
)
// Use in tests
func setupTestProvider() godi.Provider {
services := godi.NewCollection()
services.AddModules(
TestModule, // Use test module instead
AppModule,
)
provider, _ := services.Build()
return provider
}
Module Isolation
func TestUserModule(t *testing.T) {
services := godi.NewCollection()
// Add only required dependencies
services.AddSingleton(NewMockDatabase)
services.AddSingleton(NewTestLogger)
// Add module under test
services.AddModules(users.Module)
provider, _ := services.Build()
defer provider.Close()
// Test module in isolation
userService := godi.MustResolve[UserService](provider)
// ... test userService
}
Real-World Example
E-commerce Application
// Infrastructure module
var InfrastructureModule = godi.NewModule("infrastructure",
godi.AddSingleton(NewConfig),
godi.AddSingleton(NewLogger),
godi.AddSingleton(NewDatabase),
godi.AddSingleton(NewCache),
godi.AddSingleton(NewMessageQueue),
)
// Auth module
var AuthModule = godi.NewModule("auth",
godi.AddSingleton(NewJWTService),
godi.AddScoped(NewAuthMiddleware),
godi.AddScoped(NewSessionManager),
)
// Product catalog module
var CatalogModule = godi.NewModule("catalog",
godi.AddScoped(NewProductRepository),
godi.AddScoped(NewCategoryRepository),
godi.AddScoped(NewCatalogService),
)
// Shopping cart module
var CartModule = godi.NewModule("cart",
godi.AddScoped(NewCartRepository),
godi.AddScoped(NewCartService),
godi.AddTransient(NewCartCalculator),
)
// Order module
var OrderModule = godi.NewModule("orders",
godi.AddScoped(NewOrderRepository),
godi.AddScoped(NewOrderService),
godi.AddScoped(NewPaymentProcessor),
godi.AddScoped(NewShippingService),
)
// Main application
func main() {
services := godi.NewCollection()
services.AddModules(
InfrastructureModule,
AuthModule,
CatalogModule,
CartModule,
OrderModule,
)
provider, _ := services.Build()
defer provider.Close()
// Start HTTP server
server := NewServer(provider)
server.Start(":8080")
}
Best Practices
Group related services - Keep modules focused
Use clear naming - Module names should describe their purpose
Document dependencies - Make it clear what each module needs
Keep modules small - Easier to test and understand
Avoid circular dependencies - Modules shouldn’t depend on each other circularly
Test modules independently - Each module should be testable in isolation
Module Patterns
Plugin System
type Plugin interface {
Name() string
Initialize() error
}
func LoadPlugins(dir string) []godi.ModuleOption {
var modules []godi.ModuleOption
files, _ := os.ReadDir(dir)
for _, file := range files {
if strings.HasSuffix(file.Name(), ".so") {
plugin := loadPlugin(file.Name())
module := godi.NewModule(plugin.Name(),
godi.AddSingleton(func() Plugin { return plugin }),
)
modules = append(modules, module)
}
}
return modules
}
Environment-Specific Modules
func GetEnvironmentModules(env string) []godi.ModuleOption {
common := []godi.ModuleOption{
CoreModule,
BusinessModule,
}
switch env {
case "development":
return append(common,
DevDatabaseModule,
MockPaymentModule,
DebugModule,
)
case "staging":
return append(common,
StagingDatabaseModule,
TestPaymentModule,
MonitoringModule,
)
case "production":
return append(common,
ProductionDatabaseModule,
RealPaymentModule,
MonitoringModule,
AlertingModule,
)
}
return common
}
Next Steps
Explore Keyed Services
Learn about Service Groups
Understand Parameter Objects