This document explains the complete App lifecycle in host-http framework and how to implement proper service initialization using the ServiceProvider builder pattern.
The host-http framework, built on top of github.com/Bofry/host, provides a
complete application lifecycle management system. This allows you to properly
initialize, configure, and cleanup your application and its services.
The framework recognizes and calls the following methods on your App struct (if implemented):
Purpose: Basic app initialization When Called: First method called during startup Use Case: Initialize basic components that don't depend on configuration
func (app *App) Init() {
// Basic initialization
fmt.Printf("App starting up...\n")
}Purpose: Configuration and service setup When Called: After Init(), with configuration loaded Use Case: Initialize ServiceProvider and configure services
func (app *App) OnInit() {
// Initialize ServiceProvider with dependency injection
if app.ServiceProvider != nil {
app.ServiceProvider.Init(app.Config, app)
}
}Purpose: Finalization after all initialization is complete When Called: After all components are initialized Use Case: Final setup steps, validation
func (app *App) OnInitComplete() {
fmt.Printf("All initialization completed\n")
}Purpose: Server startup notification When Called: When the HTTP server starts listening Use Case: Register runtime services, log startup information
func (app *App) OnStart(ctx context.Context) {
fmt.Printf("Server started on %s\n", app.Config.ListenAddress)
}Purpose: Graceful shutdown When Called: When the server receives shutdown signal Use Case: Cleanup resources, stop services gracefully
func (app *App) OnStop(ctx context.Context) {
if app.ServiceProvider != nil {
app.ServiceProvider.Stop()
}
}The framework calls these methods in the following order:
- ConfigureLogger() (if implemented) - Configure application logger
- OnInit() - Load configuration and initialize services
- Init() - Basic app initialization
- OnInitComplete() - Finalization
- OnStart(ctx) - Server startup
- [Server runs]
- OnStop(ctx) - Graceful shutdown (on SIGTERM/SIGINT)
The ServiceProvider implements the builder pattern for dependency injection and service management, similar to the reference project pattern.
type ServiceProvider struct {
services map[string]interface{}
mutex sync.RWMutex
config *Config
}
// Init initializes services in dependency order
func (sp *ServiceProvider) Init(config *Config, app *App) {
sp.config = config
// Initialize services in correct order
sp.initCoreServices(config)
sp.initApplicationServices(config)
sp.initUtilityServices(config)
}
// Stop gracefully stops all services
func (sp *ServiceProvider) Stop() {
// Stop services in reverse dependency order
sp.stopServices()
}// RegisterService registers a service
func (sp *ServiceProvider) RegisterService(name string, service interface{}) {
sp.mutex.Lock()
defer sp.mutex.Unlock()
sp.services[name] = service
}
// GetService retrieves a service
func (sp *ServiceProvider) GetService(name string) interface{} {
sp.mutex.RLock()
defer sp.mutex.RUnlock()
return sp.services[name]
}Here's a complete App structure template:
type App struct {
Host *hosthttp.Host `host:""`
Config *Config `config:""`
ServiceProvider *ServiceProvider `serviceProvider:""`
}
type Config struct {
ListenAddress string `yaml:"ListenAddress" env:"HTTP_LISTEN_ADDRESS" default:":8080"`
EnableCompress bool `yaml:"EnableCompress" env:"ENABLE_COMPRESS" default:"false"`
ServerName string `yaml:"ServerName" env:"SERVER_NAME" default:"host-http-app"`
}
type ServiceProvider struct {
services map[string]interface{}
mutex sync.RWMutex
config *Config
}Initialize services in the correct dependency order:
- Core infrastructure services first (config, logging)
- Application services second (business logic)
- Utility services last (caching, monitoring)
Handle errors gracefully during initialization:
func (sp *ServiceProvider) Init(config *Config, app *App) {
if err := sp.initCoreServices(config); err != nil {
log.Fatalf("Failed to initialize core services: %v", err)
}
}Stop services in reverse dependency order:
func (sp *ServiceProvider) Stop() {
// Stop in reverse order
sp.stopUtilityServices()
sp.stopApplicationServices()
sp.stopCoreServices()
}Use environment variables with defaults:
type Config struct {
DatabaseURL string `env:"DATABASE_URL" required:"true"`
Port string `env:"PORT" default:"8080"`
LogLevel string `env:"LOG_LEVEL" default:"info"`
}See _examples/basic/app-lifecycle/ for a complete implementation demonstrating:
- Full lifecycle method implementation
- ServiceProvider builder pattern
- Service registration and management
- Graceful shutdown handling
See _examples/basic/health-check/ for a simple implementation with basic
lifecycle methods.
If lifecycle methods aren't being called:
- Ensure methods have the correct signature
- Check that your App struct has the correct field tags
- Verify the Bofry/host framework version compatibility
If ServiceProvider methods aren't triggering:
- Ensure
OnInit()callsServiceProvider.Init() - Ensure
OnStop()callsServiceProvider.Stop() - Check for nil pointer access
If you get "address already in use" errors:
# Kill processes using the port
lsof -ti:8080 | xargs -r kill -9
# Or use different ports
HTTP_LISTEN_ADDRESS=:9090 go run .Common environment variables for configuration:
| Variable | Description | Default |
|---|---|---|
HTTP_LISTEN_ADDRESS |
Server listen address | :8080 |
ENABLE_COMPRESS |
Enable HTTP compression | false |
SERVER_NAME |
Server identification | host-http-app |
LOG_LEVEL |
Logging level | info |
The host-http framework integrates with Bofry/host using:
- Dependency injection for App components
- Automatic configuration loading
- Service lifecycle management
- Graceful shutdown handling
This provides a robust foundation for building production-ready HTTP applications with proper service management and cleanup.