first commit
This commit is contained in:
+102
@@ -0,0 +1,102 @@
|
||||
// Command api is the entrypoint of the application. Its job is narrow and
|
||||
// deliberate: load configuration, construct every shared dependency
|
||||
// exactly once (logger, database, session manager), build the router, and
|
||||
// run an HTTP server with a graceful shutdown sequence. It contains no
|
||||
// business logic itself - that all lives in the packages it wires
|
||||
// together.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.hamidsoltani.com/hamid/go-simple-api/internal/config"
|
||||
"git.hamidsoltani.com/hamid/go-simple-api/internal/database"
|
||||
"git.hamidsoltani.com/hamid/go-simple-api/internal/logging"
|
||||
"git.hamidsoltani.com/hamid/go-simple-api/internal/router"
|
||||
"git.hamidsoltani.com/hamid/go-simple-api/internal/session"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 1. Configuration - read once, pass down everywhere as a value.
|
||||
cfg := config.Load()
|
||||
|
||||
// 2. Logger - built before anything else so even early startup
|
||||
// failures (DB connection, etc.) get logged as structured JSON,
|
||||
// consistent with every other log line the app produces.
|
||||
logger := logging.New()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// 3. Database - connect and verify with a real ping before doing
|
||||
// anything else. If this fails, there's no point starting an HTTP
|
||||
// server that can't actually serve any authenticated route.
|
||||
db, err := database.NewMySQL(ctx, cfg)
|
||||
if err != nil {
|
||||
logger.Error("failed to connect to database", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// Closes the connection pool once main() returns - i.e. after the
|
||||
// graceful shutdown sequence below completes.
|
||||
defer db.Close()
|
||||
logger.Info("connected to database", "host", cfg.DBHost, "db", cfg.DBName)
|
||||
|
||||
// 4. Migrate - ensure required tables exist. See
|
||||
// internal/database/migrate.go for why this simple approach is a
|
||||
// deliberate shortcut for a learning project.
|
||||
if err := database.Migrate(ctx, db); err != nil {
|
||||
logger.Error("failed to migrate database", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("database migrated")
|
||||
|
||||
// 5. Sessions - builds the scs.SessionManager backed by Redis.
|
||||
sessions := session.New(cfg)
|
||||
logger.Info("session manager configured", "redis_addr", cfg.RedisAddr)
|
||||
|
||||
// 6. Router - assembles every handler/middleware, using the shared
|
||||
// dependencies constructed above.
|
||||
r := router.New(logger, db, sessions, cfg)
|
||||
|
||||
// We build http.Server explicitly (instead of calling
|
||||
// http.ListenAndServe directly) specifically so we can call
|
||||
// srv.Shutdown(ctx) on it later, for a graceful shutdown.
|
||||
srv := &http.Server{
|
||||
Addr: ":" + cfg.Port,
|
||||
Handler: r,
|
||||
}
|
||||
|
||||
// ListenAndServe blocks forever (until the server stops or errors), so
|
||||
// it must run in its own goroutine - otherwise the code below that
|
||||
// listens for OS shutdown signals would never get a chance to run.
|
||||
go func() {
|
||||
logger.Info("server starting", "port", cfg.Port)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logger.Error("server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// Block the main goroutine until we receive an interrupt (Ctrl+C) or
|
||||
// termination (e.g. `docker stop`) signal from the OS.
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
|
||||
logger.Info("shutting down gracefully")
|
||||
|
||||
// Give any in-flight requests up to 5 seconds to finish before we
|
||||
// force-close them.
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
logger.Error("forced shutdown", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.Info("server stopped")
|
||||
}
|
||||
Reference in New Issue
Block a user