- Add bootstrap package to initialize application components including logger, tracer, and HTTP server. - Create config package to load runtime settings from environment variables. - Implement observability features including logging, metrics, and tracing. - Add health check and hello use cases with corresponding HTTP handlers. - Introduce middleware for request ID, access logging, metrics, and recovery. - Set up HTTP router with defined routes for health and hello endpoints. - Include tests for health and hello endpoints to ensure proper functionality. - Add OpenTelemetry collector configuration for trace exporting.
110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|