backend-service/internal/app/app.go

212 lines
5.4 KiB
Go
Raw Permalink Normal View History

package app
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"attune-heart-therapy/internal/config"
"attune-heart-therapy/internal/container"
"attune-heart-therapy/internal/logger"
"attune-heart-therapy/internal/server"
)
// Application represents the main application
type Application struct {
config *config.Config
container *container.Container
server *server.Server
log *logger.Logger
}
// New creates a new application instance
func New() (*Application, error) {
// Initialize logger first
log := logger.New("application")
// Load configuration
cfg, err := config.Load()
if err != nil {
return nil, fmt.Errorf("failed to load configuration: %w", err)
}
// Set log level based on configuration
logLevel := logger.INFO
switch cfg.Logging.Level {
case "DEBUG":
logLevel = logger.DEBUG
case "INFO":
logLevel = logger.INFO
case "WARN":
logLevel = logger.WARN
case "ERROR":
logLevel = logger.ERROR
case "FATAL":
logLevel = logger.FATAL
}
logger.SetGlobalLevel(logLevel)
log.Info("Application configuration loaded", map[string]interface{}{
"server_host": cfg.Server.Host,
"server_port": cfg.Server.Port,
"db_host": cfg.Database.Host,
"db_name": cfg.Database.Name,
})
// Initialize dependency injection container
cont := container.New(cfg)
// Initialize server
srv := server.New(cfg)
return &Application{
config: cfg,
container: cont,
server: srv,
log: log,
}, nil
}
// Initialize performs application initialization
func (app *Application) Initialize() error {
app.log.Info("Initializing application...")
// Initialize all dependencies through the container
if err := app.container.Initialize(); err != nil {
return fmt.Errorf("failed to initialize dependencies: %w", err)
}
// Wire the container to the server
app.server.SetContainer(app.container)
app.log.Info("Application initialization completed successfully")
return nil
}
// Run starts the application and handles graceful shutdown
func (app *Application) Run() error {
// Initialize the application
if err := app.Initialize(); err != nil {
return fmt.Errorf("application initialization failed: %w", err)
}
// Setup graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// Start server in a goroutine
serverErrors := make(chan error, 1)
go func() {
app.log.Info("Starting HTTP server...")
if err := app.server.Start(); err != nil {
serverErrors <- fmt.Errorf("server start failed: %w", err)
}
}()
// Wait for either shutdown signal or server error
select {
case err := <-serverErrors:
return err
case sig := <-quit:
app.log.Info("Received shutdown signal", map[string]interface{}{
"signal": sig.String(),
})
}
// Graceful shutdown
return app.shutdown()
}
// shutdown performs graceful shutdown of all components
func (app *Application) shutdown() error {
app.log.Info("Initiating graceful shutdown...")
// Create shutdown context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
var shutdownErrors []error
// Shutdown server first
if app.server != nil {
app.log.Info("Shutting down HTTP server...")
if err := app.server.Shutdown(ctx); err != nil {
app.log.Error("Error shutting down server", err)
shutdownErrors = append(shutdownErrors, fmt.Errorf("server shutdown error: %w", err))
} else {
app.log.Info("HTTP server shutdown completed")
}
}
// Shutdown application dependencies
if app.container != nil {
app.log.Info("Shutting down application dependencies...")
if err := app.container.Shutdown(); err != nil {
app.log.Error("Error shutting down dependencies", err)
shutdownErrors = append(shutdownErrors, err)
} else {
app.log.Info("Dependencies shutdown completed")
}
}
// Wait for context deadline or completion
select {
case <-ctx.Done():
if ctx.Err() == context.DeadlineExceeded {
app.log.Warn("Shutdown timeout exceeded, forcing exit")
shutdownErrors = append(shutdownErrors, fmt.Errorf("shutdown timeout exceeded"))
}
default:
// Shutdown completed before timeout
}
if len(shutdownErrors) > 0 {
app.log.Error("Graceful shutdown completed with errors", nil, map[string]interface{}{
"error_count": len(shutdownErrors),
})
return fmt.Errorf("shutdown completed with %d errors: %v", len(shutdownErrors), shutdownErrors)
}
app.log.Info("Graceful shutdown completed successfully")
return nil
}
// GetConfig returns the application configuration
func (app *Application) GetConfig() *config.Config {
return app.config
}
// GetContainer returns the dependency injection container
func (app *Application) GetContainer() *container.Container {
return app.container
}
// HealthCheck performs a comprehensive health check
func (app *Application) HealthCheck() map[string]interface{} {
health := map[string]interface{}{
"status": "ok",
"timestamp": time.Now().UTC().Format(time.RFC3339),
"service": "Video Conference Booking System",
"version": "1.0.0",
}
// Check container health
if app.container != nil {
containerHealth := app.container.HealthCheck()
health["components"] = containerHealth
// Update overall status based on component health
if status, ok := containerHealth["status"].(string); ok && status != "ok" {
health["status"] = status
}
} else {
health["status"] = "error"
health["error"] = "Container not initialized"
}
return health
}