Modules

Modules help organize large applications by grouping related service registrations. Think of them as packages for your DI configuration.

Why Modules?

Without modules, all registrations end up in one place:

// main.go - gets messy fast
services := godi.NewCollection()
services.AddSingleton(NewLogger)
services.AddSingleton(NewConfig)
services.AddSingleton(NewDatabasePool)
services.AddSingleton(NewRedisClient)
services.AddScoped(NewUserRepository)
services.AddScoped(NewOrderRepository)
services.AddScoped(NewProductRepository)
services.AddScoped(NewUserService)
services.AddScoped(NewOrderService)
services.AddScoped(NewPaymentService)
services.AddScoped(NewNotificationService)
// ... 50 more lines

With modules, you organize by domain:

// main.go - clean and organized
services := godi.NewCollection()
services.AddModules(
    infrastructure.Module,
    users.Module,
    orders.Module,
    payments.Module,
)

Creating Modules

Use godi.NewModule to create a module with a name and service registrations:

// users/module.go
package users

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

var Module = godi.NewModule("users",
    godi.AddScoped(NewUserRepository),
    godi.AddScoped(NewUserService),
    godi.AddScoped(NewUserController),
)

Module Organization

A typical application structure:

myapp/
├── main.go
├── infrastructure/
│   ├── module.go         # Database, logging, config
│   ├── database.go
│   ├── logger.go
│   └── config.go
├── users/
│   ├── module.go         # User-related services
│   ├── repository.go
│   ├── service.go
│   └── controller.go
├── orders/
│   ├── module.go         # Order-related services
│   ├── repository.go
│   ├── service.go
│   └── controller.go
└── payments/
    ├── module.go         # Payment-related services
    ├── gateway.go
    └── service.go

Example: Infrastructure Module

// infrastructure/module.go
package infrastructure

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

var Module = godi.NewModule("infrastructure",
    godi.AddSingleton(NewConfig),       // Configuration - load once, share everywhere
    godi.AddSingleton(NewLogger),       // Logging - singleton, thread-safe
    godi.AddSingleton(NewDatabasePool), // Database pool - singleton, manages connections
    godi.AddSingleton(NewRedisClient),  // Redis - singleton, connection pool
)
// infrastructure/config.go
package infrastructure

type Config struct {
    DatabaseURL string
    RedisURL    string
    Debug       bool
}

func NewConfig() *Config {
    return &Config{
        DatabaseURL: os.Getenv("DATABASE_URL"),
        RedisURL:    os.Getenv("REDIS_URL"),
        Debug:       os.Getenv("DEBUG") == "true",
    }
}

Example: Domain Module

// users/module.go
package users

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

var Module = godi.NewModule("users",
    godi.AddScoped(NewUserRepository),  // Repository - scoped, uses transaction
    godi.AddScoped(NewUserService),     // Service - scoped, business logic
    godi.AddScoped(NewUserController),  // Controller - scoped, HTTP handlers
)
// users/service.go
package users

type UserService struct {
    repo   *UserRepository
    logger *infrastructure.Logger
}

func NewUserService(repo *UserRepository, logger *infrastructure.Logger) *UserService {
    return &UserService{repo: repo, logger: logger}
}

Composing Modules

Main.go stays clean:

// main.go
package main

import (
    "github.com/junioryono/godi/v4"
    "myapp/infrastructure"
    "myapp/users"
    "myapp/orders"
    "myapp/payments"
)

func main() {
    services := godi.NewCollection()

    // Add all modules
    services.AddModules(
        infrastructure.Module,
        users.Module,
        orders.Module,
        payments.Module,
    )

    provider, err := services.Build()
    if err != nil {
        log.Fatal(err)
    }
    defer provider.Close()

    // Start application
    // ...
}

Module Dependencies

Modules can depend on services from other modules:

// orders/service.go
package orders

type OrderService struct {
    orderRepo   *OrderRepository
    userService *users.UserService  // From users module
    logger      *infrastructure.Logger  // From infrastructure module
}

func NewOrderService(
    orderRepo *OrderRepository,
    userService *users.UserService,
    logger *infrastructure.Logger,
) *OrderService {
    return &OrderService{
        orderRepo:   orderRepo,
        userService: userService,
        logger:      logger,
    }
}

godi resolves cross-module dependencies automatically. Registration order of modules doesn’t matter.

Conditional Modules

Enable modules based on configuration:

func main() {
    services := godi.NewCollection()
    services.AddModules(infrastructure.Module)

    cfg := loadConfig()

    if cfg.EnableUsers {
        services.AddModules(users.Module)
    }

    if cfg.EnablePayments {
        services.AddModules(payments.Module)
    }

    // ...
}

Testing with Modules

Replace modules for testing:

// users/module_test.go
var TestModule = godi.NewModule("users-test",
    godi.AddScoped(NewMockUserRepository), // Use mock repository
    godi.AddScoped(NewUserService),
)

func TestUserService(t *testing.T) {
    services := godi.NewCollection()
    services.AddModules(TestModule)

    provider, _ := services.Build()
    defer provider.Close()

    // Test with mock
    // ...
}

Best Practices

1. One Module Per Domain

users/module.go      # User-related services
orders/module.go     # Order-related services
payments/module.go   # Payment-related services

2. Infrastructure in Its Own Module

// infrastructure/module.go
var Module = godi.NewModule("infrastructure",
    godi.AddSingleton(NewConfig),
    godi.AddSingleton(NewLogger),
    godi.AddSingleton(NewDatabasePool),
)

3. Keep Modules Focused

// Good: focused on one area
var UserModule = godi.NewModule("users", ...)
var OrderModule = godi.NewModule("orders", ...)

// Bad: kitchen sink module
var EverythingModule = godi.NewModule("everything", ...)

4. Document Cross-Module Dependencies

// orders/module.go
package orders

// Module registers order-related services.
// Requires: infrastructure.Module, users.Module
var Module = godi.NewModule("orders",
    godi.AddScoped(NewOrderRepository),
    godi.AddScoped(NewOrderService),
)

Next: Explore framework integrations or advanced features