package shoppingbasketapp import ( "context" "fmt" "git.gocasts.ir/ebhomengo/niki/adapter/redis" logger "git.gocasts.ir/ebhomengo/niki/logger" "git.gocasts.ir/ebhomengo/niki/pkg/httpserver" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/delivery/http" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/repository" "git.gocasts.ir/ebhomengo/niki/shoppingbasketapp/service/cart" "log/slog" "os" "os/signal" "sync" "syscall" ) type Application struct { Repo repository.Repo Service cart.Service Handler http.Handler Server http.Server Config Config Logger *slog.Logger } func Setup(cfg Config) (Application, error) { l := logger.New(cfg.Logger, &slog.HandlerOptions{ Level: slog.LevelDebug, }) adapter := redis.New(cfg.Redis) repo := repository.New(adapter.Client(), cfg.Repo) validator := cart.NewValidate() svc := cart.New(validator, l, repo) handler := http.NewHandler(svc) httpServer, err := httpserver.New(cfg.HTTPServer) if err != nil { 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, Logger: l, }, 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() app.Logger.Info("Shutdown signal received...") shutdownTimeoutCtx, cancel := context.WithTimeout(context.Background(), app.Config.HTTPServer.ShutdownTimeout) defer cancel() if app.shutdownServers(shutdownTimeoutCtx) { app.Logger.Info("Servers shutdown gracefully") } else { app.Logger.Warn("Shutdown timed out, exiting application") return } wg.Wait() app.Logger.Info("shopping-basket-app stopped") } func startServers(app Application, wg *sync.WaitGroup) { wg.Add(1) go func() { defer wg.Wait() app.Logger.Info(fmt.Sprintf("HTTP server starting on port: %d", app.Config.HTTPServer.Port)) if err := app.Server.Serve(); err != nil { app.Logger.Error(fmt.Sprintf("error listen and serve HTTP server on port %d", app.Config.HTTPServer.Port)) } app.Logger.Info(fmt.Sprintf("HTTP server stopped on port %d", app.Config.HTTPServer.Port)) }() } func (app Application) shutdownServers(ctx context.Context) bool { app.Logger.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) app.Logger.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) { app.Logger.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 { app.Logger.Error(fmt.Sprintf("failed http server gracefully shutdown: %v", err)) } app.Logger.Info("Successfully http server gracefully shutdown") }