Modules

Modules are the fundamental building blocks of Goose applications. They encapsulate related functionality and promote code organization.

Module Interface

Every Goose module implements the Module interface:

type Module interface {
    Imports() []Module      // Modules this module depends on
    Exports() []any         // Services available to other modules
    Declarations() []any    // Components provided by this module
}

Creating a Module

Basic Module

package users

import "github.com/awesome-goose/goose/types"

type UsersModule struct{}

func (m *UsersModule) Imports() []types.Module {
    return []types.Module{}
}

func (m *UsersModule) Exports() []any {
    return []any{
        &UsersService{},
    }
}

func (m *UsersModule) Declarations() []any {
    return []any{
        &UsersController{},
        &UsersService{},
    }
}

Using the CLI

Generate a module with the Goose CLI:

# Plain module
goose g module --name=users --type=plain

# Resource module with entity
goose g module --name=products --type=resource

Module Components

Declarations

Declarations are components that belong to this module:

func (m *UsersModule) Declarations() []any {
    return []any{
        &UsersController{},
        &UsersService{},
        &UsersRepository{},
        &AuthMiddleware{},
    }
}

Exports

Exports are services available to other modules:

func (m *UsersModule) Exports() []any {
    return []any{
        &UsersService{}, // Other modules can inject this
    }
}

Imports

Imports are dependencies on other modules:

func (m *OrdersModule) Imports() []types.Module {
    return []types.Module{
        &users.UsersModule{},     // Need users service
        &products.ProductsModule{}, // Need products service
    }
}

Module Dependencies

Importing Services

When you import a module, its exported services become available:

// orders/orders.service.go
type OrdersService struct {
    usersService    *users.UsersService    `inject:""`
    productsService *products.ProductsService `inject:""`
}

func (s *OrdersService) CreateOrder(userID string, productIDs []string) (*Order, error) {
    user, err := s.usersService.GetByID(userID)
    if err != nil {
        return nil, err
    }
    // Use the imported services...
}

Circular Dependencies

Avoid circular dependencies between modules:

โŒ Bad:
UsersModule โ†’ OrdersModule โ†’ UsersModule

โœ… Good:
UsersModule โ†’ SharedModule
OrdersModule โ†’ SharedModule

Solution: Extract shared functionality into a separate module.

Global Modules

Global modules are automatically available to all modules:

type SharedModule struct{}

func (m *SharedModule) IsGlobal() bool {
    return true
}

func (m *SharedModule) Exports() []any {
    return []any{
        &LoggingService{},
        &CacheService{},
    }
}

Built-in Modules

Goose provides several built-in modules:

SQL Module

import "github.com/awesome-goose/goose/modules/sql"

func (m *AppModule) Imports() []types.Module {
    return []types.Module{
        sql.NewModule(
            sql.WithDriver("sqlite"),
            sql.WithDatabase("./data/app.db"),
        ),
    }
}

Cache Module

import "github.com/awesome-goose/goose/modules/cache"

func (m *AppModule) Imports() []types.Module {
    return []types.Module{
        cache.NewModule(
            cache.WithDefaultTTL(15 * time.Minute),
        ),
    }
}

Queue Module

import "github.com/awesome-goose/goose/modules/queues"

func (m *AppModule) Imports() []types.Module {
    return []types.Module{
        queues.NewModule(
            queues.WithQueue("default"),
        ),
    }
}

KV Module

import "github.com/awesome-goose/goose/modules/kv"

func (m *AppModule) Imports() []types.Module {
    return []types.Module{
        kv.NewModule(
            kv.WithGroup("app"),
        ),
    }
}

Cron Module

import "github.com/awesome-goose/goose/modules/cron"

func (m *AppModule) Imports() []types.Module {
    return []types.Module{
        cron.NewModule(),
    }
}

Module Best Practices

1. Single Responsibility

Each module should handle one feature:

// โœ… Good: Focused module
type AuthModule struct{}     // Authentication only
type UsersModule struct{}    // User management only
type EmailModule struct{}    // Email sending only

// โŒ Bad: Too many responsibilities
type UtilsModule struct{}    // Authentication + Users + Email

2. Minimal Exports

Only export what other modules need:

func (m *UsersModule) Exports() []any {
    return []any{
        &UsersService{},      // โœ… Public API
        // &UsersRepository{}, // โŒ Internal, don't export
    }
}

3. Clear Dependencies

Import only what you need:

func (m *OrdersModule) Imports() []types.Module {
    return []types.Module{
        &users.UsersModule{},    // โœ… Actually needed
        // &email.EmailModule{}, // โŒ Not used here
    }
}

4. Testable Design

Design modules for easy testing:

// Module with interface for testing
type UsersServiceInterface interface {
    GetByID(id string) (*User, error)
}

// Concrete implementation
type UsersService struct {
    db *sql.Db `inject:""`
}

func (s *UsersService) GetByID(id string) (*User, error) {
    // Implementation
}

Module Structure

Recommended file organization:

users/
โ”œโ”€โ”€ users.module.go        # Module definition
โ”œโ”€โ”€ users.controller.go    # HTTP handlers
โ”œโ”€โ”€ users.service.go       # Business logic
โ”œโ”€โ”€ users.repository.go    # Data access (optional)
โ”œโ”€โ”€ users.routes.go        # Route definitions
โ”œโ”€โ”€ users.dtos.go          # Data transfer objects
โ”œโ”€โ”€ users.entity.go        # Database entity
โ”œโ”€โ”€ users.middleware.go    # Module middleware
โ””โ”€โ”€ users_test.go          # Tests

Next Steps