package main import ( "context" "fmt" "os" "time" "zee/internal/helper/config" "zee/internal/helper/database" "zee/internal/helper/feature" "zee/internal/helper/logger" "zee/internal/pkg/lifecycle" "zee/internal/transport/http" "gorm.io/gorm" ) // HTTPService implements the lifecycle.Service interface for the HTTP server type HTTPService struct { server *http.Server cfg *config.Config db *database.Database } func NewHTTPService(cfg *config.Config, db *database.Database) *HTTPService { var gormDB *gorm.DB if db != nil { gormDB = db.DB } return &HTTPService{ server: http.NewServer(cfg, gormDB), // gormDB can be nil here cfg: cfg, db: db, // db itself (the *database.Database wrapper) can also be nil } } func (s *HTTPService) Name() string { return "HTTP Server" } func (s *HTTPService) Start() error { // Tạo channel để nhận lỗi từ goroutine errChan := make(chan error, 1) // Khởi động server trong goroutine go func() { logger.Infof("Đang khởi động %s trên %s:%d...", s.Name(), s.cfg.Server.Host, s.cfg.Server.Port) if err := s.server.Start(); err != nil { logger.WithError(err).Error("Lỗi HTTP server") errChan <- fmt.Errorf("lỗi HTTP server: %w", err) return } errChan <- nil }() // Chờ server khởi động hoặc báo lỗi select { case err := <-errChan: return err // Trả về lỗi nếu có case <-time.After(5 * time.Second): // Nếu sau 5 giây không có lỗi, coi như server đã khởi động thành công logger.Infof("%s đã khởi động thành công trên %s:%d", s.Name(), s.cfg.Server.Host, s.cfg.Server.Port) return nil } } func (s *HTTPService) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) } func main() { // Initialize config loader configLoader := config.NewConfigLoader() // Load configuration cfg, err := configLoader.Load() if err != nil { fmt.Printf("Failed to load configuration: %v\n", err) os.Exit(1) } // Initialize logger logger.Init(&logger.LogConfig{ Level: cfg.Logger.Level, Format: "json", EnableCaller: true, ReportCaller: true, }) // Initialize feature flags if err := feature.Init(); err != nil { logger.WithError(err).Fatal("Failed to initialize feature flags") } // Print application info logger.Infof("Starting %s v%s", cfg.App.Name, cfg.App.Version) logger.Infof("Environment: %s", cfg.App.Environment) logger.Infof("Log Level: %s", cfg.Logger.Level) logger.Infof("Timezone: %s", cfg.App.Timezone) // Print server config logger.Infof("Server config: %s:%d", cfg.Server.Host, cfg.Server.Port) logger.Infof("Read Timeout: %d seconds", cfg.Server.ReadTimeout) logger.Infof("Write Timeout: %d seconds", cfg.Server.WriteTimeout) logger.Infof("Shutdown Timeout: %d seconds", cfg.Server.ShutdownTimeout) // Create a new lifecycle manager shutdownTimeout := 30 * time.Second // Default shutdown timeout if cfg.Server.ShutdownTimeout > 0 { shutdownTimeout = time.Duration(cfg.Server.ShutdownTimeout) * time.Second } lifecycleMgr := lifecycle.New(shutdownTimeout) var gormDB *gorm.DB // Declare gormDB for database connection var dbInstance *database.Database // Declare dbInstance for HTTPService and other potential users if feature.IsEnabled(feature.EnableDatabase) { logger.Info("Feature flag 'enable_database' is true. Initializing database connection...") // Initialize database connection var dbErr error gormDB, dbErr = database.NewConnection(&cfg.Database) if dbErr != nil { logger.WithError(dbErr).Fatal("Failed to connect to database") } // Run database migrations if err := database.Migrate(cfg.Database); err != nil { // Migrate still needs cfg.Database for path etc. logger.WithError(err).Fatal("Failed to migrate database") } // Register database cleanup on shutdown lifecycleMgr.Register(&databaseService{db: gormDB}) // Prepare dbInstance for HTTPService dbInstance = &database.Database{DB: gormDB, Config: &cfg.Database} } else { logger.Info("Feature flag 'enable_database' is false. Skipping database initialization.") // gormDB remains nil // dbInstance remains nil // No databaseService is registered with lifecycleMgr if DB is disabled. } // Initialize HTTP service // NewHTTPService and its chain (http.NewServer, http.SetupRouter) // must be able to handle a nil dbInstance or a dbInstance with a nil DB. // The NewHTTPService function already checks if its db argument (or db.DB) is nil. httpService := NewHTTPService(cfg, dbInstance) // dbInstance can be nil here if httpService == nil { logger.Fatal("Failed to create HTTP service") } lifecycleMgr.Register(httpService) // HTTP service is always registered // Start all services logger.Info("Đang khởi động các dịch vụ...") if err := lifecycleMgr.Start(); err != nil { logger.WithError(err).Fatal("Lỗi nghiêm trọng: Không thể khởi động các dịch vụ") } // Handle OS signals for graceful shutdown in a separate goroutine go lifecycleMgr.ShutdownOnSignal() // Wait for all services to complete if err := lifecycleMgr.Wait(); err != nil { logger.WithError(err).Error("Service error") } // Close feature flags if err := feature.Close(); err != nil { logger.WithError(err).Error("Failed to close feature flags") } logger.Info("Application stopped") } // databaseService implements the lifecycle.Service interface for database operations type databaseService struct { db *gorm.DB } func (s *databaseService) Name() string { return "Database Service" } func (s *databaseService) Start() error { // Database initialization is handled in main return nil } func (s *databaseService) Shutdown(ctx context.Context) error { if s.db != nil { sqlDB, err := s.db.DB() if err != nil { return fmt.Errorf("failed to get database instance: %w", err) } return sqlDB.Close() } return nil }