Component Definition
Learn how to define and configure IoC components using Go IoC's Spring-like syntax.
Component Markers
Go IoC uses struct markers to identify components. These are empty struct fields that serve as "annotations":
type MyService struct {
Component struct{} // Marks this struct as an IoC component
}
Interface Implementation
Components can implement interfaces using the Implements marker:
type UserService interface {
GetUser(id string) (*User, error)
}
type UserServiceImpl struct {
Component struct{}
Implements struct{} `implements:"UserService"`
}
func (s *UserServiceImpl) GetUser(id string) (*User, error) {
// Implementation here
return &User{ID: id}, nil
}
Qualifiers
When multiple components implement the same interface, use qualifiers to distinguish them:
type EmailService struct {
Component struct{}
Implements struct{} `implements:"MessageService"`
Qualifier struct{} `value:"email"` // Qualifier for this implementation
}
type SmsService struct {
Component struct{}
Implements struct{} `implements:"MessageService"`
Qualifier struct{} `value:"sms"` // Different qualifier
}
Dependency Injection
Use the autowired tag to inject dependencies:
type NotificationService struct {
Component struct{}
// Inject by interface type
Logger logger.Logger `autowired:"true"`
// Inject with qualifier
EmailSender message.MessageService `autowired:"true" qualifier:"email"`
SmsSender message.MessageService `autowired:"true" qualifier:"sms"`
}
Constructor Functions
Go IoC can detect and use constructor functions automatically:
type DatabaseService struct {
Component struct{}
connectionString string
}
// Constructor function - will be used for initialization
func NewDatabaseService() *DatabaseService {
return &DatabaseService{
connectionString: "default-connection",
}
}
Lifecycle Methods
Components can define lifecycle methods for initialization and cleanup:
type CacheService struct {
Component struct{}
cache map[string]interface{}
}
// Called after dependency injection is complete
func (s *CacheService) PostConstruct() error {
s.cache = make(map[string]interface{})
fmt.Println("Cache initialized")
return nil
}
// Called during application shutdown
func (s *CacheService) PreDestroy() error {
s.cache = nil
fmt.Println("Cache cleaned up")
return nil
}
Complete Example
Here's a comprehensive example showing all features:
package service
type Logger interface {
Log(message string)
}
type ConsoleLogger struct {
Component struct{}
Implements struct{} `implements:"Logger"`
Qualifier struct{} `value:"console"`
}
func (l *ConsoleLogger) Log(message string) {
fmt.Println("CONSOLE:", message)
}
type FileLogger struct {
Component struct{}
Implements struct{} `implements:"Logger"`
Qualifier struct{} `value:"file"`
filename string
}
func NewFileLogger() *FileLogger {
return &FileLogger{filename: "app.log"}
}
func (l *FileLogger) Log(message string) {
// Write to file implementation
}
func (l *FileLogger) PostConstruct() error {
fmt.Println("File logger initialized with file:", l.filename)
return nil
}
type UserService struct {
Component struct{}
// Dependencies
ConsoleLogger Logger `autowired:"true" qualifier:"console"`
FileLogger Logger `autowired:"true" qualifier:"file"`
Database DatabaseInterface `autowired:"true"`
}
func (s *UserService) CreateUser(name string) {
s.ConsoleLogger.Log("Creating user: " + name)
s.FileLogger.Log("User created: " + name)
// Database operations...
}
Best Practices
1. Use Interfaces
Always define interfaces for your services to enable loose coupling:
// Good
type UserService interface {
CreateUser(name string) error
GetUser(id string) (*User, error)
}
type UserServiceImpl struct {
Component struct{}
Implements struct{} `implements:"UserService"`
}
2. Meaningful Qualifiers
Use descriptive qualifiers that clearly indicate the purpose:
// Good
Qualifier struct{} `value:"primary-database"`
Qualifier struct{} `value:"cache-redis"`
Qualifier struct{} `value:"smtp-email"`
// Avoid generic names
Qualifier struct{} `value:"impl1"`
Qualifier struct{} `value:"service"`
3. Constructor Functions
Use constructor functions for complex initialization:
func NewDatabaseService(config *Config) *DatabaseService {
return &DatabaseService{
connectionString: config.DatabaseURL,
maxConnections: config.MaxConnections,
}
}
4. Lifecycle Methods
Use lifecycle methods for resource management:
func (s *DatabaseService) PostConstruct() error {
return s.connect()
}
func (s *DatabaseService) PreDestroy() error {
return s.disconnect()
}
Validation
Go IoC provides comprehensive validation of your component configuration:
# Validate without generating files
iocgen --dry-run
# Show detailed validation information
iocgen --dry-run --verbose
# List all discovered components
iocgen --list
Common validation checks include:
- Missing interface implementations
- Unresolved dependencies
- Circular dependencies
- Qualifier conflicts
- Invalid struct tag syntax