Auto Configuration
Introduction
Hiboot auto-configuration automatically configures your application based on the packages you import. When you import a starter package, Hiboot detects it and configures the necessary components for dependency injection.
This approach is inspired by Spring Boot’s auto-configuration mechanism, adapted for Go’s package system.
How Auto-Configuration Works
- Import a starter package in your application
- The starter’s
init()function registers its configuration - Hiboot scans registered configurations at startup
- Configuration methods are called to create injectable components
- Components become available for dependency injection
Built-in Starters
Hiboot provides several built-in starters:
| Starter | Package | Description |
|---|---|---|
| Actuator | github.com/hidevopsio/hiboot/pkg/starter/actuator |
Health checks and metrics |
| Logging | github.com/hidevopsio/hiboot/pkg/starter/logging |
Structured logging |
| JWT | github.com/hidevopsio/hiboot/pkg/starter/jwt |
JWT authentication |
| Locale | github.com/hidevopsio/hiboot/pkg/starter/locale |
Internationalization |
Using a Starter
package main
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/app/web"
"github.com/hidevopsio/hiboot/pkg/starter/actuator"
"github.com/hidevopsio/hiboot/pkg/starter/logging"
)
func main() {
web.NewApplication().
SetProperty(app.ProfilesInclude, actuator.Profile, logging.Profile).
Run()
}
Creating Your Own Starter
A starter typically contains:
- Configuration struct - Embeds
app.Configurationand defines factory methods - Properties struct - Holds configuration values from YAML files
- Service structs - The actual components to be injected
Project Structure
starter/
└── myservice/
├── autoconfigure.go # Configuration and registration
├── properties.go # Properties struct
├── service.go # Service implementation
└── doc.go # Package documentation
Step 1: Define Properties
Properties hold configuration values that can be customized in application.yml:
package myservice
// Properties holds the configuration for MyService
type Properties struct {
Enabled bool `json:"enabled" default:"true"`
Endpoint string `json:"endpoint" default:"http://localhost:8080"`
Timeout int `json:"timeout" default:"30"`
MaxRetry int `json:"max_retry" default:"3"`
}
Step 2: Define the Service
package myservice
import "fmt"
// Service is the interface for MyService
type Service interface {
DoSomething(data string) (string, error)
}
// serviceImpl is the implementation
type serviceImpl struct {
endpoint string
timeout int
maxRetry int
}
func newService(endpoint string, timeout, maxRetry int) Service {
return &serviceImpl{
endpoint: endpoint,
timeout: timeout,
maxRetry: maxRetry,
}
}
func (s *serviceImpl) DoSomething(data string) (string, error) {
return fmt.Sprintf("Processed: %s", data), nil
}
Step 3: Create the Configuration
package myservice
import "github.com/hidevopsio/hiboot/pkg/app"
const (
// Profile is the profile name for this starter
Profile = "myservice"
)
// configuration is the auto-configuration struct
type configuration struct {
app.Configuration
// Properties will be populated from application.yml
// The field name should match the mapstructure tag
MyServiceProperties Properties `mapstructure:"myservice"`
}
func init() {
// Register the configuration
app.Register(newConfiguration)
}
func newConfiguration() *configuration {
return &configuration{}
}
// Service creates and returns the MyService instance
// This method is called by Hiboot during startup
func (c *configuration) Service() Service {
return newService(
c.MyServiceProperties.Endpoint,
c.MyServiceProperties.Timeout,
c.MyServiceProperties.MaxRetry,
)
}
Step 4: Configure in application.yml
app:
profiles:
include:
- myservice
myservice:
enabled: true
endpoint: "https://api.example.com"
timeout: 60
max_retry: 5
Step 5: Use in Your Application
package controller
import (
"github.com/hidevopsio/hiboot/pkg/app"
"github.com/hidevopsio/hiboot/pkg/at"
"github.com/hidevopsio/hiboot/pkg/model"
"myapp/starter/myservice"
)
type myController struct {
at.RestController
at.RequestMapping `value:"/api"`
service myservice.Service
}
func init() {
app.Register(newMyController)
}
func newMyController(service myservice.Service) *myController {
return &myController{
service: service,
}
}
func (c *myController) Post(request *DataRequest) (model.Response, error) {
result, err := c.service.DoSomething(request.Data)
response := new(model.BaseResponse)
response.SetData(result)
return response, err
}
Advanced Configuration
Conditional Configuration
Use struct tags to control when configuration is applied:
type configuration struct {
app.Configuration `after:"databaseConfiguration"`
// This configuration loads after databaseConfiguration
}
Available tags:
| Tag | Description |
|---|---|
after:"name" |
Load after the specified configuration |
missing:"name" |
Only load if the specified configuration is missing |
Multiple Bean Methods
A configuration can provide multiple injectable components:
type configuration struct {
app.Configuration
Props Properties `mapstructure:"myservice"`
}
// Primary service
func (c *configuration) Service() Service {
return newService(c.Props)
}
// Client for external API
func (c *configuration) Client() *Client {
return newClient(c.Props.Endpoint)
}
// Repository with database connection
func (c *configuration) Repository(db *Database) Repository {
return newRepository(db)
}
Injecting Dependencies in Configuration
Configuration methods can have parameters that will be injected:
// Repository depends on Database which is provided by another starter
func (c *configuration) Repository(db *gorm.DB) Repository {
return &gormRepository{db: db}
}
// Service depends on Repository
func (c *configuration) Service(repo Repository) Service {
return &serviceImpl{repository: repo}
}
Best Practices
- Use interfaces: Define service interfaces for better testability
- Provide defaults: Use
default:""tags for sensible defaults - Document properties: Clearly document what each property does
- Keep it focused: Each starter should provide one cohesive feature
- Handle errors gracefully: Return meaningful errors during initialization
What’s Next?
- Inversion of Control - Deep dive into dependency injection
- Web Application - Build REST APIs
- CLI Application - Build command-line tools