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
- Dependency Injection - How services are injected
- Controllers - Building controllers
- Services - Service layer patterns