package shoppingbasketapp import ( "context" "fmt" "git.gocasts.ir/ebhomengo/niki/adapter/redis" "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/repository" "git.gocasts.ir/ebhomengo/niki/domain/shoppingbasket/service" "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" "git.gocasts.ir/ebhomengo/niki/pkg/logger" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/delivery/http" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/delivery/http/cart" "os" "os/signal" "sync" "syscall" ) type Application struct { Repo repository.Repo Service service.Service Handler cart.Handler Server http.Server Config Config } func Setup(ctx context.Context, cfg Config) (Application, error) { adapter := redis.New(cfg.Redis) repo := repository.New(adapter.Client(), cfg.Repo) validator := service.NewValidate() svc := service.New(validator, repo) handler := cart.NewHandler(svc) httpServer, err := httpserver.New(cfg.HTTPServer) if err != nil { logger.L().Error("failed to initialize http server", "error", err) return Application{}, err } server := http.NewServer(handler, httpServer) return Application{ Repo: repo, Service: svc, Handler: handler, Server: server, Config: cfg, }, nil } func (app Application) Start() { var wg sync.WaitGroup ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() startServers(app, &wg) <-ctx.Done() logger.L().Info("Shutdown signal received...") shutdownTimeoutCtx, cancel := context.WithTimeout(context.Background(), app.Config.HTTPServer.ShutdownTimeout) defer cancel() if app.shutdownServers(shutdownTimeoutCtx) { logger.L().Info("Servers shutdown gracefully") } else { logger.L().Warn("Shutdown timed out, exiting application") return } wg.Wait() logger.L().Info("shopping-basket-app stopped") } func startServers(app Application, wg *sync.WaitGroup) { wg.Add(1) go func() { defer wg.Wait() logger.L().Info(fmt.Sprintf("HTTP server starting on port: %d", app.Config.HTTPServer.Port)) if err := app.Server.Serve(); err != nil { logger.L().Error(fmt.Sprintf("error listen and serve HTTP server on port %d", app.Config.HTTPServer.Port)) } logger.L().Info(fmt.Sprintf("HTTP server stopped on port %d", app.Config.HTTPServer.Port)) }() } func (app Application) shutdownServers(ctx context.Context) bool { logger.L().Info("Starting server shutdown process...") shutdownDone := make(chan struct{}) go func() { var shutdownWg sync.WaitGroup shutdownWg.Add(1) go app.shutdownHTTPServe(ctx, &shutdownWg) shutdownWg.Wait() close(shutdownDone) logger.L().Info("All servers have been shut down successfully.") }() select { case <-shutdownDone: return true case <-ctx.Done(): return false } } func (app Application) shutdownHTTPServe(parentCtx context.Context, wg *sync.WaitGroup) { logger.L().Info(fmt.Sprintf("Starting gracefully shutdown for http server on port %d", app.Config.HTTPServer.Port)) defer wg.Done() httpShutdownCtx, httpCancel := context.WithTimeout(parentCtx, app.Config.HTTPServer.ShutdownTimeout) defer httpCancel() if err := app.Server.Stop(httpShutdownCtx); err != nil { logger.L().Error(fmt.Sprintf("failed http server gracefully shutdown: %v", err)) } logger.L().Info("Successfully http server gracefully shutdown") }