Refactor server architecture: move internal logic to pkg/server package

- Simplify cmd/server/main.go from 158 to 57 lines (64% reduction)

- Move HTTP server creation, graceful shutdown, and context management to pkg/server

- Add Run() method to encapsulate server lifecycle management

- Maintain all existing functionality and OpenTelemetry integration

- Improve separation of concerns and code organization

This refactoring makes cmd/server/main.go a thin entrypoint while moving all server implementation details to the pkg/server package, following Go best practices for package organization.

Add server and greet binaries to .gitignore
This commit is contained in:
2026-04-04 12:46:31 +02:00
parent 36f0b79b90
commit 9855f521f3
4 changed files with 114 additions and 108 deletions

View File

@@ -2,11 +2,17 @@ package server
import (
"context"
"net"
"net/http"
"os/signal"
"syscall"
"time"
"DanceLessonsCoach/pkg/config"
"DanceLessonsCoach/pkg/greet"
"DanceLessonsCoach/pkg/telemetry"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
@@ -14,9 +20,11 @@ import (
)
type Server struct {
router *chi.Mux
readyCtx context.Context
withOTEL bool
router *chi.Mux
readyCtx context.Context
withOTEL bool
config *config.Config
tracerProvider *sdktrace.TracerProvider
}
func NewServer(cfg *config.Config, readyCtx context.Context) *Server {
@@ -24,6 +32,7 @@ func NewServer(cfg *config.Config, readyCtx context.Context) *Server {
router: chi.NewRouter(),
readyCtx: readyCtx,
withOTEL: cfg.GetTelemetryEnabled(),
config: cfg,
}
s.setupRoutes()
return s
@@ -95,3 +104,99 @@ func (s *Server) handleReadiness(w http.ResponseWriter, r *http.Request) {
func (s *Server) Router() http.Handler {
return s.router
}
// Run starts the HTTP server and handles graceful shutdown
func (s *Server) Run() error {
// Initialize OpenTelemetry if enabled
var err error
if s.withOTEL {
log.Info().Msg("Initializing OpenTelemetry tracing")
telemetrySetup := &telemetry.Setup{
ServiceName: s.config.GetServiceName(),
OTLPEndpoint: s.config.GetOTLPEndpoint(),
Insecure: s.config.GetTelemetryInsecure(),
SamplerType: s.config.GetSamplerType(),
SamplerRatio: s.config.GetSamplerRatio(),
}
if s.tracerProvider, err = telemetrySetup.InitializeTracing(context.Background()); err != nil {
log.Error().Err(err).Msg("Failed to initialize OpenTelemetry, continuing without tracing")
s.withOTEL = false
} else {
log.Info().Msg("OpenTelemetry tracing initialized successfully")
}
}
// Setup signal context for graceful shutdown
rootCtx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
// Create ongoing context for active requests
ongoingCtx, stopOngoingGracefully := context.WithCancel(context.Background())
defer stopOngoingGracefully()
// Create HTTP server
log.Info().Str("address", s.config.GetServerAddress()).Msg("Server running")
srv := &http.Server{
Addr: s.config.GetServerAddress(),
Handler: s.router,
BaseContext: func(_ net.Listener) context.Context {
return ongoingCtx
},
}
// Start the HTTP server in a separate goroutine
serverErrors := make(chan error, 1)
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
serverErrors <- err
}
close(serverErrors)
}()
// Wait for signal
<-rootCtx.Done()
stop()
log.Info().Msg("Shutdown signal received")
// Cancel readiness context to stop accepting new requests
if cancelReady, ok := s.readyCtx.(interface{ Cancel() }); ok {
cancelReady.Cancel()
}
log.Info().Msg("Readiness set to false, no longer accepting new requests")
// Give time for readiness check to propagate
time.Sleep(1 * time.Second)
log.Info().Msg("Readiness check propagated, now waiting for ongoing requests to finish.")
// Create shutdown context with timeout from config
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), s.config.Shutdown.Timeout)
defer shutdownCancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Error().Err(err).Msg("Server shutdown failed")
return err
}
log.Info().Msg("Server shutdown complete")
// Shutdown OpenTelemetry tracer provider
if s.tracerProvider != nil {
if err := telemetry.Shutdown(context.Background(), s.tracerProvider); err != nil {
log.Error().Err(err).Msg("Failed to shutdown OpenTelemetry tracer provider")
} else {
log.Info().Msg("OpenTelemetry tracer provider shutdown complete")
}
}
// Force log flush
time.Sleep(100 * time.Millisecond)
// Return any server errors
if err, ok := <-serverErrors; ok {
return err
}
return nil
}