Parameter Objects
Parameter objects simplify constructors with many dependencies using godi.In and godi.Out.
Input Parameters (In)
Use godi.In when a constructor has many dependencies:
Basic Example
// Instead of this long constructor:
func NewUserService(
db Database,
cache Cache,
logger Logger,
emailService EmailService,
smsService SMSService,
config *Config,
) *UserService {
// ...
}
// Use a parameter object:
type UserServiceParams struct {
godi.In
DB Database
Cache Cache
Logger Logger
EmailService EmailService
SMSService SMSService
Config *Config
}
func NewUserService(params UserServiceParams) *UserService {
return &UserService{
db: params.DB,
cache: params.Cache,
logger: params.Logger,
emailService: params.EmailService,
smsService: params.SMSService,
config: params.Config,
}
}
Optional Dependencies
Mark dependencies as optional with the optional tag:
type ServiceParams struct {
godi.In
DB Database
Logger Logger
Cache Cache `optional:"true"` // Might be nil
Tracer Tracer `optional:"true"` // Might be nil
}
func NewService(params ServiceParams) *Service {
svc := &Service{
db: params.DB,
logger: params.Logger,
}
// Check optional dependencies
if params.Cache != nil {
svc.cache = params.Cache
}
if params.Tracer != nil {
svc.tracer = params.Tracer
}
return svc
}
Named Dependencies
Use specific implementations with the name tag:
type DatabaseParams struct {
godi.In
Primary Database `name:"primary"`
Secondary Database `name:"secondary"`
Analytics Database `name:"analytics" optional:"true"`
}
func NewUserRepository(params DatabaseParams) *UserRepository {
return &UserRepository{
primary: params.Primary,
secondary: params.Secondary,
analytics: params.Analytics, // Might be nil
}
}
Groups
Collect all services of a type with the group tag:
type ValidatorParams struct {
godi.In
Validators []Validator `group:"validators"`
}
func NewValidationService(params ValidatorParams) *ValidationService {
return &ValidationService{
validators: params.Validators, // Gets all validators
}
}
// Register validators
var ValidationModule = godi.NewModule("validation",
godi.AddSingleton(NewEmailValidator, godi.Group("validators")),
godi.AddSingleton(NewPhoneValidator, godi.Group("validators")),
godi.AddSingleton(NewAddressValidator, godi.Group("validators")),
)
Output Parameters (Out)
Use godi.Out to register multiple services from one constructor:
Basic Example
// Return multiple repositories from one constructor
type RepositoryBundle struct {
godi.Out
UserRepo UserRepository
OrderRepo OrderRepository
ProductRepo ProductRepository
}
func NewRepositories(db Database, logger Logger) RepositoryBundle {
return RepositoryBundle{
UserRepo: &userRepository{db: db, logger: logger},
OrderRepo: &orderRepository{db: db, logger: logger},
ProductRepo: &productRepository{db: db, logger: logger},
}
}
// Register once, get three services!
var DataModule = godi.NewModule("data",
godi.AddSingleton(NewDatabase),
godi.AddSingleton(NewLogger),
godi.AddScoped(NewRepositories), // Registers all three repos
)
Named Outputs
Register services with specific names:
type CacheBundle struct {
godi.Out
UserCache Cache `name:"user-cache"`
ProductCache Cache `name:"product-cache"`
OrderCache Cache `name:"order-cache"`
}
func NewCaches() CacheBundle {
return CacheBundle{
UserCache: NewRedisCache("user:"),
ProductCache: NewRedisCache("product:"),
OrderCache: NewRedisCache("order:"),
}
}
Groups in Output
Add services to groups:
type HandlerBundle struct {
godi.Out
UserHandler http.Handler `group:"handlers"`
OrderHandler http.Handler `group:"handlers"`
ProductHandler http.Handler `group:"handlers"`
}
func NewHandlers(/* deps */) HandlerBundle {
return HandlerBundle{
UserHandler: &userHandler{/* ... */},
OrderHandler: &orderHandler{/* ... */},
ProductHandler: &productHandler{/* ... */},
}
}
Real-World Example
Complete example showing both In and Out:
// Input parameters for configuration
type AppConfigParams struct {
godi.In
Env string `name:"environment"`
Databases struct {
Primary Database `name:"primary-db"`
Secondary Database `name:"secondary-db"`
}
Caches []Cache `group:"caches"`
}
// Output bundle for related services
type AppServices struct {
godi.Out
HealthCheck http.Handler `group:"handlers"`
MetricsRoute http.Handler `group:"handlers"`
UserService UserService
ProductService ProductService
OrderService OrderService `name:"order-svc"`
}
func NewAppServices(params AppConfigParams) AppServices {
// Use all the inputs to create outputs
return AppServices{
HealthCheck: NewHealthHandler(params.Databases.Primary),
MetricsRoute: NewMetricsHandler(),
UserService: NewUserService(params.Databases.Primary),
ProductService: NewProductService(params.Databases.Secondary),
OrderService: NewOrderService(params.Databases.Primary),
}
}
Best Practices
Use In for 4+ dependencies - Keeps constructors clean
Use Out for related services - Group initialization logic
Document optional fields - Make it clear what can be nil
Keep parameter objects focused - Don’t create a “god object”
When to Use
✅ Use parameter objects when:
Constructor has many parameters (4+)
You have optional dependencies
You need named or grouped services
You want to bundle related outputs
❌ Don’t use when:
Constructor has few parameters (1-3)
Dependencies are simple
It makes the code harder to understand
Parameter objects are a tool for managing complexity - use them when they make your code cleaner!