package bootstrap import ( "context" "errors" "fmt" "log/slog" "net/http" "strings" "time" "github.com/gin-gonic/gin" "switchbot-api/internal/app/config" "switchbot-api/internal/app/observability" "switchbot-api/internal/domain/health" "switchbot-api/internal/domain/hello" httptransport "switchbot-api/internal/transport/http" "switchbot-api/internal/transport/http/handlers" ) type App struct { Config config.Config Logger *slog.Logger Server *http.Server tracerShutdown func(ctx context.Context) error } func New(ctx context.Context, cfg config.Config) (*App, error) { logger, err := observability.NewLogger(cfg.LogLevel) if err != nil { return nil, err } setGinMode(cfg.Environment) tp, err := observability.NewTracerProvider(ctx, logger, cfg.ServiceName, cfg.Environment, cfg.OTLPEndpoint) if err != nil { return nil, fmt.Errorf("initialize tracing: %w", err) } tracer := observability.NewTracer(tp, cfg.ServiceName) promRegistry := observability.NewRegistry() httpMetrics := observability.NewHTTPMetrics(promRegistry) promHandler := observability.NewPrometheusHandler(promRegistry) helloUsecase := hello.NewService() readinessUsecase := health.NewReadinessUsecase(nil) healthHandler := handlers.NewHealthHandler(readinessUsecase) helloHandler := handlers.NewHelloHandler(helloUsecase) router := httptransport.NewRouter(httptransport.Dependencies{ Logger: logger, Tracer: tracer, HTTPMetrics: httpMetrics, PrometheusHandler: promHandler, HealthHandler: healthHandler, HelloHandler: helloHandler, }) server := &http.Server{ Addr: cfg.Addr(), Handler: router, ReadHeaderTimeout: 5 * time.Second, ReadTimeout: 15 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } return &App{ Config: cfg, Logger: logger, Server: server, tracerShutdown: func(ctx context.Context) error { return tp.Shutdown(ctx) }, }, nil } func (a *App) Start() error { a.Logger.Info("http server starting", slog.String("addr", a.Server.Addr), slog.String("service", a.Config.ServiceName), slog.String("env", a.Config.Environment)) if err := a.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } return nil } func (a *App) Shutdown(ctx context.Context) error { var errs []error if err := a.Server.Shutdown(ctx); err != nil { errs = append(errs, fmt.Errorf("shutdown http server: %w", err)) } if err := a.tracerShutdown(ctx); err != nil { errs = append(errs, fmt.Errorf("shutdown tracer provider: %w", err)) } return errors.Join(errs...) } func setGinMode(environment string) { switch strings.ToLower(strings.TrimSpace(environment)) { case "local", "dev", "development", "test": gin.SetMode(gin.DebugMode) default: gin.SetMode(gin.ReleaseMode) } }