Provider Functions
Understanding provider function patterns and Wire integration
Provider Functions
Provider functions are the foundation of dependency injection in Taskw. They define how your application components are created and wired together using Google's Wire library.
Overview
Provider functions are constructor functions that create and configure your application's dependencies. Taskw scans for these functions and generates Wire-compatible dependency injection code.
Basic Provider Pattern
Simple Provider
func ProvideUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
Provider with Configuration
func ProvideUserService(config *Config) *UserService {
return &UserService{
dbURL: config.DatabaseURL,
timeout: config.Timeout,
}
}
Provider Function Requirements
1. Must Have "Provide" Prefix
// ✅ Correct - function name starts with "Provide"
func ProvideUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
// ✅ Also correct - function name starts with "Provide"
func ProvideDatabase(config *Config) (*gorm.DB, error) {
return gorm.Open(postgres.Open(config.DatabaseURL), &gorm.Config{})
}
// ❌ Incorrect - function name doesn't start with "Provide"
func NewUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
2. Must Return Concrete Types
// ✅ Returns concrete type
func ProvideUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
// ❌ Returns interface (not supported)
func ProvideUserHandler(service *UserService) UserHandler {
return &userHandler{service: service}
}
3. Must Be Constructor Functions
// ✅ Constructor function
func ProvideUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
// ❌ Not a constructor
func (h *UserHandler) GetUser(id string) (*User, error) {
// Implementation
}
Dependency Injection Patterns
Struct Constructor Injection
type UserHandler struct {
service *UserService
}
func ProvideUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
Interface Implementation
// Define interface
type UserRepository interface {
FindByID(id string) (*User, error)
}
// Implementation
type UserRepositoryImpl struct {
db *gorm.DB
}
func ProvideUserRepository(db *gorm.DB) *UserRepository {
return &UserRepositoryImpl{db: db}
}
Common Provider Patterns
Handler Providers
func ProvideUserHandler(service *UserService) *UserHandler {
return &UserHandler{service: service}
}
func ProvideOrderHandler(service *OrderService) *OrderHandler {
return &OrderHandler{service: service}
}
func ProvideHealthHandler() *HealthHandler {
return &HealthHandler{}
}
Service Providers
func ProvideUserService(repo *UserRepository) *UserService {
return &UserService{repo: repo}
}
func ProvideOrderService(repo *OrderRepository) *OrderService {
return &OrderService{repo: repo}
}
func ProvideEmailService(config *Config) *EmailService {
return &EmailService{
smtpHost: config.SMTPHost,
smtpPort: config.SMTPPort,
}
}
Wire Integration
Generated Provider Sets
Taskw generates Wire provider sets from your provider functions with "Provide" prefix:
// Generated dependencies_gen.go
package api
import (
"github.com/google/wire"
"your-project/internal/handlers"
"your-project/internal/services"
"your-project/internal/repositories"
)
var ProviderSet = wire.NewSet(
handlers.ProvideUserHandler,
handlers.ProvideOrderHandler,
handlers.ProvideHealthHandler,
services.ProvideUserService,
services.ProvideOrderService,
repositories.ProvideUserRepository,
repositories.ProvideOrderRepository,
)
Wire Usage
Use the generated provider set in your Wire setup:
// wire.go
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
"your-project/internal/api"
)
func InitializeServer() (*api.Server, error) {
wire.Build(
api.ProviderSet,
// Add any additional providers here
)
return &api.Server{}, nil
}
Build Tags
Generated files include proper build tags:
//go:build !wireinject
// +build !wireinject
package api
// Generated code here
Error Handling
Error handling is important for providers that can fail. Based on the Wire documentation, providers should return an error if they fail to create the dependency.
// ✅ Proper error handling
func ProvideDatabase(config *Config) (*gorm.DB, error) {
db, err := gorm.Open(postgres.Open(config.DatabaseURL), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
return db, nil
}
// ❌ Ignoring errors
func ProvideDatabase(config *Config) *gorm.DB {
db, _ := gorm.Open(postgres.Open(config.DatabaseURL), &gorm.Config{})
return db
}
Cleanups
For providers that need to close resources, you can return a cleanup function. Based on the Wire documentation, providers should close resources if they are not needed anymore.
func ProvideDatabase(config *Config) (*gorm.DB, func(), error) {
db, err := gorm.Open(postgres.Open(config.DatabaseURL), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
return db, func() {
db.Close()
}, nil
}
Troubleshooting
Common Issues
Provider not found: Ensure function name starts with "Provide"
Missing dependencies: Verify all required dependencies are available
Debugging Commands
# Scan to see what providers are detected
taskw scan
# Generate dependency injection code
taskw generate deps
# Check for Wire errors
go generate ./...
Validation
Taskw validates providers and reports issues:
❌ Validation errors:
• Circular dependency detected: UserHandler ↔ UserService
• Missing "Provide" prefix for NewOrderHandler
• Invalid provider function: GetUser (not a constructor)