From 87bd9cfabbdc011215b6652d6f0e1c88c2b7e973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Wed, 19 Nov 2025 13:01:43 -0300 Subject: [PATCH 1/6] feat: update Docker setup with Nginx and HAProxy integration --- backend/Dockerfile | 18 ++++++++++ backend/Makefile | 7 +++- backend/docker-compose.yml | 74 ++++++++++++++++++++++++++++++++------ backend/nginx/nginx.conf | 70 ++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 12 deletions(-) create mode 100644 backend/Dockerfile create mode 100644 backend/nginx/nginx.conf diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..8529fa3 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,18 @@ +FROM golang:1.24.10-alpine AS builder + +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download && go mod verify + +COPY . . + +RUN go build -o main main.go + +FROM alpine:latest + +WORKDIR /app + +COPY --from=builder /app/main . + +CMD ["./main"] \ No newline at end of file diff --git a/backend/Makefile b/backend/Makefile index 1e0ece9..799d18e 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -29,4 +29,9 @@ lint: .PHONY: fmt format: @gofmt -w . - @gofumpt -w . \ No newline at end of file + @gofumpt -w . + +# compose up with 3 cassandra nodes and 4 app nodes +.PHONY: compose-up +compose-up: + @docker compose up -d --scale cassandra=3 --scale app=4 \ No newline at end of file diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index e456c37..393430c 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,23 +1,69 @@ services: - redis: - image: redis:latest - container_name: lnk-redis + nginx: + image: nginx:alpine + container_name: lnk-nginx ports: - - "6379:6379" + - "8888:80" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - app + networks: + - lnk-network - cassandra: - image: cassandra:latest - container_name: lnk-cassandra + app: + build: . + environment: + - CASSANDRA_HOSTS=cassandra-lb:9042 + - REDIS_HOST=redis:6379 + - PORT=8080 + volumes: + - ./.env:/app/.env:ro + networks: + - lnk-network + depends_on: + - cassandra-lb + - redis + deploy: + replicas: 4 + env_file: + - .env + + cassandra-lb: + image: haproxy:2.8-alpine + container_name: lnk-cassandra-lb ports: - "9042:9042" + - "8404:8404" + volumes: + - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro + depends_on: + - cassandra + networks: + - lnk-network + + cassandra: + image: cassandra:latest environment: - CASSANDRA_CLUSTER_NAME=lnk-cluster - CASSANDRA_DC=datacenter1 - - CASSANDRA_RACK=rack1 + - CASSANDRA_ENDPOINT_SNITCH=GossipingPropertyFileSnitch - CASSANDRA_AUTHENTICATOR=PasswordAuthenticator - CASSANDRA_AUTHORIZER=CassandraAuthorizer - CASSANDRA_USER=cassandra - CASSANDRA_PASSWORD=cassandra + volumes: + - cassandra-data:/var/lib/cassandra + networks: + - lnk-network + + redis: + image: redis:latest + container_name: lnk-redis + ports: + - "6379:6379" + networks: + - lnk-network grafana: image: grafana/otel-lgtm:latest @@ -27,6 +73,12 @@ services: - "4317:4317" environment: - GF_SECURITY_ADMIN_PASSWORD=admin - depends_on: - - cassandra - - redis + networks: + - lnk-network + +volumes: + cassandra-data: + +networks: + lnk-network: + driver: bridge diff --git a/backend/nginx/nginx.conf b/backend/nginx/nginx.conf new file mode 100644 index 0000000..82c29f0 --- /dev/null +++ b/backend/nginx/nginx.conf @@ -0,0 +1,70 @@ +# nginx/nginx.conf +events { + worker_connections 1024; +} + +http { + # Log format para debugging + log_format upstream_log '$time_iso8601 | $remote_addr | "$request" | ' + 'status $status | upstream $upstream_addr | ' + 'response_time $request_time'; + + # Upstream para as instâncias da aplicação + upstream lnk_backend { + least_conn; # Algoritmo de balanceamento + server app:8080 max_fails=3 fail_timeout=30s; + keepalive 32; + } + + # Rate limiting + limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + + server { + listen 80; + server_name localhost; + + # Logs + access_log /var/log/nginx/access.log upstream_log; + error_log /var/log/nginx/error.log; + + # Health check endpoint + location /nginx-health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # API endpoints + location /api/ { + limit_req zone=api burst=20 nodelay; + + proxy_pass http://lnk_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout 5s; + proxy_send_timeout 10s; + proxy_read_timeout 10s; + + # Headers para debugging + add_header X-Upstream-Server $upstream_addr; + add_header X-Response-Time $request_time; + } + + # Redirect para URLs curtas + location / { + proxy_pass http://lnk_backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Cache para URLs válidas + proxy_cache_valid 200 302 10m; + proxy_cache_valid 404 1m; + } + } +} From 383fd4f6a02fa38d0eee765cce84a7f3ba28ab86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Wed, 19 Nov 2025 13:47:27 -0300 Subject: [PATCH 2/6] feat: add migrator service and enhance Docker setup --- backend/Dockerfile | 11 +++++--- backend/Makefile | 8 ++++++ backend/{ => cmd/app}/main.go | 2 +- backend/cmd/migrator/main.go | 45 +++++++++++++++++++++++++++++++++ backend/docker-compose.yml | 42 +++++++++++++++++++++++++++--- backend/gateways/gocql/setup.go | 7 ++--- backend/haproxy/haproxy.cfg | 33 ++++++++++++++++++++++++ 7 files changed, 138 insertions(+), 10 deletions(-) rename backend/{ => cmd/app}/main.go (99%) create mode 100644 backend/cmd/migrator/main.go create mode 100644 backend/haproxy/haproxy.cfg diff --git a/backend/Dockerfile b/backend/Dockerfile index 8529fa3..ecd68ba 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,12 +7,17 @@ RUN go mod download && go mod verify COPY . . -RUN go build -o main main.go +# Build both migrator and app binaries +RUN go build -o migrator ./cmd/migrator/main.go +RUN go build -o app ./cmd/app/main.go FROM alpine:latest WORKDIR /app -COPY --from=builder /app/main . +# Copy both binaries +COPY --from=builder /app/migrator . +COPY --from=builder /app/app . -CMD ["./main"] \ No newline at end of file +# Default to running the app +CMD ["./app"] \ No newline at end of file diff --git a/backend/Makefile b/backend/Makefile index 799d18e..0d246a4 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -30,6 +30,14 @@ lint: format: @gofmt -w . @gofumpt -w . + +.PHONY: run-migrator +run-migrator: + @docker compose run migrator + +.PHONY: run-app +run-app: + @docker compose run app # compose up with 3 cassandra nodes and 4 app nodes .PHONY: compose-up diff --git a/backend/main.go b/backend/cmd/app/main.go similarity index 99% rename from backend/main.go rename to backend/cmd/app/main.go index 7f9f2a8..0fd2223 100644 --- a/backend/main.go +++ b/backend/cmd/app/main.go @@ -85,7 +85,7 @@ func setupConfigAndLogger() (*config.Config, *zap.Logger) { } func setupDatabase(cfg *config.Config, appLogger *zap.Logger) *gocql.Session { - session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, appLogger) + session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, appLogger, false) if err != nil { appLogger.Fatal("Failed to setup database", zap.Error(err)) } diff --git a/backend/cmd/migrator/main.go b/backend/cmd/migrator/main.go new file mode 100644 index 0000000..2d7cf7f --- /dev/null +++ b/backend/cmd/migrator/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "log" + + "lnk/extensions/config" + "lnk/extensions/logger" + gocqlPackage "lnk/gateways/gocql" + + gocql "github.com/apache/cassandra-gocql-driver/v2" + "go.uber.org/zap" +) + +func main() { + cfg, appLogger := setupConfigAndLogger() + + session := setupDatabase(cfg, appLogger) + defer session.Close() + +} + +func setupConfigAndLogger() (*config.Config, *zap.Logger) { + cfg, err := config.LoadConfig() + if err != nil { + log.Fatalf("Failed to load config: %v", err) + } + + appLogger, err := logger.NewLogger(cfg.Logger) + if err != nil { + log.Fatalf("Failed to create logger: %v", err) + } + + appLogger.Info("Starting application") + + return cfg, appLogger +} + +func setupDatabase(cfg *config.Config, appLogger *zap.Logger) *gocql.Session { + session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, appLogger, cfg.Gocql.AutoMigrate) + if err != nil { + appLogger.Fatal("Failed to setup database", zap.Error(err)) + } + + return session +} diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 393430c..56d76b2 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -11,6 +11,21 @@ services: networks: - lnk-network + migrator: + build: . + container_name: lnk-migrator + command: ["./migrator"] + volumes: + - ./.env:/app/.env:ro + networks: + - lnk-network + depends_on: + cassandra-lb: + condition: service_healthy + env_file: + - .env + restart: "no" + app: build: . environment: @@ -22,8 +37,12 @@ services: networks: - lnk-network depends_on: - - cassandra-lb - - redis + migrator: + condition: service_completed_successfully + cassandra-lb: + condition: service_healthy + redis: + condition: service_started deploy: replicas: 4 env_file: @@ -38,7 +57,13 @@ services: volumes: - ./haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro depends_on: - - cassandra + cassandra: + condition: service_healthy + healthcheck: + test: ["CMD", "nc", "-z", "localhost", "9042"] + interval: 5s + timeout: 3s + retries: 5 networks: - lnk-network @@ -54,6 +79,12 @@ services: - CASSANDRA_PASSWORD=cassandra volumes: - cassandra-data:/var/lib/cassandra + healthcheck: + test: ["CMD-SHELL", "nodetool status | grep -E '^UN'"] + interval: 10s + timeout: 5s + retries: 30 + start_period: 60s networks: - lnk-network @@ -62,6 +93,11 @@ services: container_name: lnk-redis ports: - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 networks: - lnk-network diff --git a/backend/gateways/gocql/setup.go b/backend/gateways/gocql/setup.go index 9e81c6c..a1b5c41 100644 --- a/backend/gateways/gocql/setup.go +++ b/backend/gateways/gocql/setup.go @@ -7,12 +7,13 @@ import ( "strconv" "time" + "lnk/gateways/gocql/migrations" + gocql "github.com/apache/cassandra-gocql-driver/v2" "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/cassandra" "github.com/golang-migrate/migrate/v4/source/iofs" "go.uber.org/zap" - "lnk/gateways/gocql/migrations" ) const ( @@ -21,7 +22,7 @@ const ( shutdownTimeout = 5 * time.Second ) -func SetupDatabase(config *Config, logger *zap.Logger) (*gocql.Session, error) { +func SetupDatabase(config *Config, logger *zap.Logger, autoMigrate bool) (*gocql.Session, error) { cluster := gocql.NewCluster(config.Host) cluster.Port = config.Port cluster.Authenticator = gocql.PasswordAuthenticator{ @@ -57,7 +58,7 @@ func SetupDatabase(config *Config, logger *zap.Logger) (*gocql.Session, error) { session.Close() session = sessionWithKeyspace - if config.AutoMigrate { + if autoMigrate { err := runMigrations(config, logger) if err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) diff --git a/backend/haproxy/haproxy.cfg b/backend/haproxy/haproxy.cfg new file mode 100644 index 0000000..4602b10 --- /dev/null +++ b/backend/haproxy/haproxy.cfg @@ -0,0 +1,33 @@ +global + log stdout format raw local0 + maxconn 4096 + +defaults + log global + mode tcp + timeout connect 5000ms + timeout client 50000ms + timeout server 50000ms + option tcplog + +# Stats endpoint +listen stats + bind *:8404 + mode http + stats enable + stats uri /stats + stats refresh 30s + +# Cassandra backend +frontend cassandra_frontend + bind *:9042 + default_backend cassandra_backend + +backend cassandra_backend + balance roundrobin + option tcp-check + tcp-check connect + tcp-check send PING\r\n + tcp-check expect string PONG + server cassandra1 cassandra:9042 check + From 4d2af2e374c213eb2422717feebdf557e36dc573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Wed, 19 Nov 2025 13:50:51 -0300 Subject: [PATCH 3/6] refactor: unify logger usage and enhance error handling in main application --- backend/cmd/app/main.go | 91 +++++++++++++++++++++--------------- backend/cmd/migrator/main.go | 24 ++++++---- 2 files changed, 69 insertions(+), 46 deletions(-) diff --git a/backend/cmd/app/main.go b/backend/cmd/app/main.go index 0fd2223..854ff93 100644 --- a/backend/cmd/app/main.go +++ b/backend/cmd/app/main.go @@ -25,39 +25,54 @@ import ( ) func main() { - cfg, appLogger := setupConfigAndLogger() + cfg, logger := setupConfigAndLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() shutdownOTel, err := setupOTelSDK(ctx, cfg) if err != nil { - appLogger.Fatal("Failed to setup OpenTelemetry", zap.Error(err)) + logger.Fatal("Failed to setup OpenTelemetry", zap.Error(err)) } defer func() { if err := shutdownOTel(ctx); err != nil { - appLogger.Error("Failed to shutdown OpenTelemetry", zap.Error(err)) + logger.Error("Failed to shutdown OpenTelemetry", zap.Error(err)) } }() - session := setupDatabase(cfg, appLogger) + session, err := setupDatabase(cfg, logger) + if err != nil { + log.Fatalf("Failed to setup database: %v", err) + } defer session.Close() - redisClient := setupRedis(ctx, cfg, appLogger) + redisClient, err := setupRedis(ctx, cfg, logger) + if err != nil { + log.Fatalf("Failed to setup Redis: %v", err) + } defer func() { err := redisClient.Close() if err != nil { - appLogger.Error("Failed to close Redis client", zap.Error(err)) + logger.Error("Failed to close Redis client", zap.Error(err)) } }() - initializeCounter(ctx, redisClient, cfg, appLogger) + setInitialCounter, err := initializeCounter(ctx, redisClient, cfg, logger) + if err != nil { + log.Fatalf("Failed to initialize counter: %v", err) + } + if !setInitialCounter { + log.Fatalf("Failed to set initial counter") + } - useCase := createUseCase(cfg, appLogger, session, redisClient) - server := createAndStartServer(cfg, appLogger, useCase) + useCase := createUseCase(cfg, logger, session, redisClient) + server, err := createAndStartServer(cfg, logger, useCase) + if err != nil { + log.Fatalf("Failed to create and start server: %v", err) + } - shutdownServer(ctx, appLogger, server) + shutdownServer(ctx, logger, server) } func setupOTelSDK(ctx context.Context, cfg *config.Config) (func(context.Context) error, error) { @@ -74,47 +89,49 @@ func setupConfigAndLogger() (*config.Config, *zap.Logger) { log.Fatalf("Failed to load config: %v", err) } - appLogger, err := logger.NewLogger(cfg.Logger) + logger, err := logger.NewLogger(cfg.Logger) if err != nil { log.Fatalf("Failed to create logger: %v", err) } - appLogger.Info("Starting application") + logger.Info("Starting application") - return cfg, appLogger + return cfg, logger } -func setupDatabase(cfg *config.Config, appLogger *zap.Logger) *gocql.Session { - session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, appLogger, false) +func setupDatabase(cfg *config.Config, logger *zap.Logger) (*gocql.Session, error) { + session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, logger, false) if err != nil { - appLogger.Fatal("Failed to setup database", zap.Error(err)) + return nil, fmt.Errorf("failed to setup database: %w", err) } - return session + return session, nil } -func setupRedis(ctx context.Context, cfg *config.Config, appLogger *zap.Logger) *redis.Client { - redisClient, err := redisPackage.SetupRedis(ctx, &cfg.Redis, appLogger) +func setupRedis(ctx context.Context, cfg *config.Config, logger *zap.Logger) (*redis.Client, error) { + redisClient, err := redisPackage.SetupRedis(ctx, &cfg.Redis, logger) if err != nil { - appLogger.Fatal("Failed to setup Redis", zap.Error(err)) + return nil, fmt.Errorf("failed to setup Redis: %w", err) } - return redisClient + return redisClient, nil } -func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *config.Config, appLogger *zap.Logger) { - setInitialCounter, err := redisPackage.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, appLogger) +func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *config.Config, logger *zap.Logger) (bool, error) { + setInitialCounter, err := redisPackage.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, logger) if err != nil && !setInitialCounter { - appLogger.Fatal("Failed to set initial counter", zap.Error(err)) + return false, fmt.Errorf("failed to set initial counter: %w", err) } + + return setInitialCounter, nil } -func createUseCase(cfg *config.Config, appLogger *zap.Logger, session *gocql.Session, redisClient *redis.Client) *usecases.UseCase { - repository := repositories.NewRepository(appLogger, session) +func createUseCase(cfg *config.Config, logger *zap.Logger, session *gocql.Session, redisClient *redis.Client) *usecases.UseCase { + repository := repositories.NewRepository(logger, session) redisAdapter := redisPackage.NewRedisAdapter(redisClient) return usecases.NewUseCase(usecases.NewUseCaseParams{ - Logger: appLogger, + Logger: logger, Repository: repository, Redis: redisAdapter, Salt: cfg.App.Base62Salt, @@ -122,32 +139,32 @@ func createUseCase(cfg *config.Config, appLogger *zap.Logger, session *gocql.Ses }) } -func createAndStartServer(cfg *config.Config, appLogger *zap.Logger, useCase *usecases.UseCase) *httpServer.Server { - httpHandlers := handlers.NewHandlers(appLogger, useCase) +func createAndStartServer(cfg *config.Config, logger *zap.Logger, useCase *usecases.UseCase) (*httpServer.Server, error) { + httpHandlers := handlers.NewHandlers(logger, useCase) router := httpServer.NewRouter(httpServer.RouterConfig{ - Logger: appLogger, + Logger: logger, GinMode: cfg.App.GinMode, Env: cfg.App.ENV, Handlers: httpHandlers, }) - server := httpServer.NewServer(appLogger, cfg.App.Port, router) + server := httpServer.NewServer(logger, cfg.App.Port, router) err := server.Start() if err != nil { - appLogger.Fatal("Failed to start HTTP server", zap.Error(err)) + return nil, fmt.Errorf("failed to start HTTP server: %w", err) } - return server + return server, nil } -func shutdownServer(ctx context.Context, appLogger *zap.Logger, server *httpServer.Server) { +func shutdownServer(ctx context.Context, logger *zap.Logger, server *httpServer.Server) { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) <-sigChan - appLogger.Info("Received shutdown signal") + logger.Info("Received shutdown signal") const shutdownTimeout = 10 * time.Second @@ -156,8 +173,8 @@ func shutdownServer(ctx context.Context, appLogger *zap.Logger, server *httpServ err := server.Shutdown(shutdownCtx) if err != nil { - appLogger.Error("Error during server shutdown", zap.Error(err)) + logger.Error("Error during server shutdown", zap.Error(err)) } - appLogger.Info("Application stopped") + logger.Info("Application stopped") } diff --git a/backend/cmd/migrator/main.go b/backend/cmd/migrator/main.go index 2d7cf7f..5b7c7de 100644 --- a/backend/cmd/migrator/main.go +++ b/backend/cmd/migrator/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "log" "lnk/extensions/config" @@ -12,11 +13,16 @@ import ( ) func main() { - cfg, appLogger := setupConfigAndLogger() + cfg, logger := setupConfigAndLogger() + + session, err := setupDatabase(cfg, logger) + if err != nil { + log.Fatalf("Failed to setup database: %v", err) + } - session := setupDatabase(cfg, appLogger) defer session.Close() + logger.Info("Migrations completed successfully") } func setupConfigAndLogger() (*config.Config, *zap.Logger) { @@ -25,21 +31,21 @@ func setupConfigAndLogger() (*config.Config, *zap.Logger) { log.Fatalf("Failed to load config: %v", err) } - appLogger, err := logger.NewLogger(cfg.Logger) + logger, err := logger.NewLogger(cfg.Logger) if err != nil { log.Fatalf("Failed to create logger: %v", err) } - appLogger.Info("Starting application") + logger.Info("Starting application") - return cfg, appLogger + return cfg, logger } -func setupDatabase(cfg *config.Config, appLogger *zap.Logger) *gocql.Session { - session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, appLogger, cfg.Gocql.AutoMigrate) +func setupDatabase(cfg *config.Config, logger *zap.Logger) (*gocql.Session, error) { + session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, logger, cfg.Gocql.AutoMigrate) if err != nil { - appLogger.Fatal("Failed to setup database", zap.Error(err)) + return nil, fmt.Errorf("failed to setup database: %w", err) } - return session + return session, nil } From f2c310485373b8f9dcc4325572c8c6b8701a9a32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Wed, 19 Nov 2025 14:40:31 -0300 Subject: [PATCH 4/6] refactor: streamline Docker configuration and enhance application error handling --- backend/cmd/app/main.go | 16 ++++++---------- backend/docker-compose.yml | 10 ++++------ backend/gateways/http/middleware/middleware.go | 7 ++++++- backend/haproxy/haproxy.cfg | 4 +--- backend/nginx/nginx.conf | 12 +----------- 5 files changed, 18 insertions(+), 31 deletions(-) diff --git a/backend/cmd/app/main.go b/backend/cmd/app/main.go index 854ff93..1658cfe 100644 --- a/backend/cmd/app/main.go +++ b/backend/cmd/app/main.go @@ -58,14 +58,10 @@ func main() { } }() - setInitialCounter, err := initializeCounter(ctx, redisClient, cfg, logger) + err = initializeCounter(ctx, redisClient, cfg, logger) if err != nil { log.Fatalf("Failed to initialize counter: %v", err) } - if !setInitialCounter { - log.Fatalf("Failed to set initial counter") - } - useCase := createUseCase(cfg, logger, session, redisClient) server, err := createAndStartServer(cfg, logger, useCase) if err != nil { @@ -117,13 +113,13 @@ func setupRedis(ctx context.Context, cfg *config.Config, logger *zap.Logger) (*r return redisClient, nil } -func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *config.Config, logger *zap.Logger) (bool, error) { - setInitialCounter, err := redisPackage.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, logger) - if err != nil && !setInitialCounter { - return false, fmt.Errorf("failed to set initial counter: %w", err) +func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *config.Config, logger *zap.Logger) error { + _, err := redisPackage.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, logger) + if err != nil { + return fmt.Errorf("failed to set initial counter: %w", err) } - return setInitialCounter, nil + return nil } func createUseCase(cfg *config.Config, logger *zap.Logger, session *gocql.Session, redisClient *redis.Client) *usecases.UseCase { diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 56d76b2..0dcf50a 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -28,10 +28,6 @@ services: app: build: . - environment: - - CASSANDRA_HOSTS=cassandra-lb:9042 - - REDIS_HOST=redis:6379 - - PORT=8080 volumes: - ./.env:/app/.env:ro networks: @@ -51,6 +47,7 @@ services: cassandra-lb: image: haproxy:2.8-alpine container_name: lnk-cassandra-lb + command: ["haproxy", "-f", "/usr/local/etc/haproxy/haproxy.cfg"] ports: - "9042:9042" - "8404:8404" @@ -60,10 +57,11 @@ services: cassandra: condition: service_healthy healthcheck: - test: ["CMD", "nc", "-z", "localhost", "9042"] + test: ["CMD-SHELL", "pgrep haproxy > /dev/null"] interval: 5s timeout: 3s - retries: 5 + retries: 10 + start_period: 10s networks: - lnk-network diff --git a/backend/gateways/http/middleware/middleware.go b/backend/gateways/http/middleware/middleware.go index 167d8f2..a6c2331 100644 --- a/backend/gateways/http/middleware/middleware.go +++ b/backend/gateways/http/middleware/middleware.go @@ -46,7 +46,12 @@ func Recovery(logger *zap.Logger) gin.HandlerFunc { func CORS() gin.HandlerFunc { return func(c *gin.Context) { - c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + origin := c.GetHeader("Origin") + if origin == "" { + origin = "*" + } + + c.Writer.Header().Set("Access-Control-Allow-Origin", origin) c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH") diff --git a/backend/haproxy/haproxy.cfg b/backend/haproxy/haproxy.cfg index 4602b10..d242b94 100644 --- a/backend/haproxy/haproxy.cfg +++ b/backend/haproxy/haproxy.cfg @@ -27,7 +27,5 @@ backend cassandra_backend balance roundrobin option tcp-check tcp-check connect - tcp-check send PING\r\n - tcp-check expect string PONG - server cassandra1 cassandra:9042 check + server cassandra cassandra:9042 check diff --git a/backend/nginx/nginx.conf b/backend/nginx/nginx.conf index 82c29f0..1445906 100644 --- a/backend/nginx/nginx.conf +++ b/backend/nginx/nginx.conf @@ -4,37 +4,31 @@ events { } http { - # Log format para debugging log_format upstream_log '$time_iso8601 | $remote_addr | "$request" | ' 'status $status | upstream $upstream_addr | ' 'response_time $request_time'; - # Upstream para as instâncias da aplicação upstream lnk_backend { - least_conn; # Algoritmo de balanceamento + least_conn; server app:8080 max_fails=3 fail_timeout=30s; keepalive 32; } - # Rate limiting limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; server { listen 80; server_name localhost; - # Logs access_log /var/log/nginx/access.log upstream_log; error_log /var/log/nginx/error.log; - # Health check endpoint location /nginx-health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } - # API endpoints location /api/ { limit_req zone=api burst=20 nodelay; @@ -44,17 +38,14 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - # Timeouts proxy_connect_timeout 5s; proxy_send_timeout 10s; proxy_read_timeout 10s; - # Headers para debugging add_header X-Upstream-Server $upstream_addr; add_header X-Response-Time $request_time; } - # Redirect para URLs curtas location / { proxy_pass http://lnk_backend; proxy_set_header Host $host; @@ -62,7 +53,6 @@ http { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - # Cache para URLs válidas proxy_cache_valid 200 302 10m; proxy_cache_valid 404 1m; } From e43b734f5173f20bed40e290edc0702ee506b650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Wed, 19 Nov 2025 15:02:07 -0300 Subject: [PATCH 5/6] feat: enhance README with observability and load balancing details; update middleware for CORS handling --- Readme.md | 349 +++++++++++------- backend/Makefile | 7 +- .../gateways/http/middleware/middleware.go | 8 +- 3 files changed, 223 insertions(+), 141 deletions(-) diff --git a/Readme.md b/Readme.md index 033eb8c..c0c5216 100644 --- a/Readme.md +++ b/Readme.md @@ -12,6 +12,9 @@ A high-performance URL shortener service built with Go, using Cassandra for pers - 📝 **API Documentation**: Swagger/OpenAPI documentation in development mode - 🏥 **Health Checks**: Built-in health check endpoint - 🧪 **Test Coverage**: Comprehensive test suite with isolated test databases +- 🔍 **Observability**: OpenTelemetry integration for distributed tracing +- ⚖️ **Load Balancing**: Nginx load balancer with 4 app replicas +- 🔄 **HAProxy**: Cassandra load balancing for high availability ## Architecture @@ -19,12 +22,41 @@ The project follows a clean architecture pattern with clear separation of concer - **Domain Layer**: Business logic and entities - **Gateway Layer**: External integrations (HTTP, Cassandra) -- **Extension Layer**: Infrastructure utilities (config, logger, Redis) +- **Extension Layer**: Infrastructure utilities (config, logger, Redis, OpenTelemetry) + +### System Architecture + +``` +┌─────────────┐ +│ Frontend │ → http://localhost:8888 +└──────┬──────┘ + │ +┌──────▼──────┐ +│ Nginx │ (Port 8888) - Load Balancer +└──────┬──────┘ + │ +┌──────▼──────┐ +│ App (x4) │ (Load balanced replicas) +└──────┬──────┘ + ┌───┴──┐ +┌──▼──┐ ┌─▼──┐ +│Redis│ │ DB │ +└─────┘ └──┬─┘ + │ + ┌────▼────┐ + │HAProxy │ (Cassandra Load Balancer) + └────┬────┘ + │ + ┌────▼────┐ + │Cassandra│ + └─────────┘ +``` + ## Prerequisites - Go 1.24 or higher -- Docker and Docker Compose (for running Cassandra and Redis) +- Docker and Docker Compose (for running all services) - Make (optional, for using Makefile commands) ## Installation @@ -35,88 +67,98 @@ git clone git@github.com:eulixir/lnk.git cd lnk ``` -2. Install dependencies: +2. Install backend dependencies: ```bash +cd backend go mod download ``` -3. Start required services using Docker Compose: -```bash -docker-compose up -d -``` - -This will start: -- Redis on port `6379` -- Cassandra on port `9042` - -> **⚠️ Important**: The Cassandra setup can take a significant amount of time (30-60 seconds or more) to fully initialize and be ready to accept connections. Wait for Cassandra to be healthy before starting the backend application. You can check readiness with: -> ```bash -> docker exec lnk-cassandra nodetool status -> ``` -> When Cassandra is ready, you should see the node status as `UN` (Up Normal). - -4. Create a `.env` file in the project root with the following configuration: +3. Copy `.env.example` to `.env` file in the `backend` directory with the following configuration: ```env -# Application Configuration -ENV=development -PORT=8080 -GIN_MODE=debug -BASE62_SALT=your-secret-salt-here - -# Cassandra Configuration -CASSANDRA_HOST=localhost +# Cassandra +CASSANDRA_HOST=cassandra-lb CASSANDRA_PORT=9042 CASSANDRA_USERNAME=cassandra CASSANDRA_PASSWORD=cassandra CASSANDRA_KEYSPACE=lnk CASSANDRA_AUTO_MIGRATE=true -# Redis Configuration -REDIS_HOST=localhost +# APP +ENV=development +PORT=8080 +GIN_MODE=debug +BASE62_SALT=banana + +# Redis +REDIS_HOST=redis REDIS_PORT=6379 REDIS_PASSWORD= REDIS_DB=0 -COUNTER_KEY=url_counter -COUNTER_START_VAL=1000000 +COUNTER_KEY=short_url_counter +COUNTER_START_VAL=14000000 + +# Log +LOG_LEVEL=debug + +# Otel +SERVICE_NAME=lnk-backend +OTEL_EXPORTER_OTLP_ENDPOINT=grafana:4317 +``` -# Logger Configuration -LOG_LEVEL=info +**Note**: +- Make sure to set a secure `BASE62_SALT` value in production +- Use Docker service names (e.g., `cassandra-lb`, `redis`, `grafana`) when running in Docker +- Use `localhost` when running services locally outside Docker + +4. Start all services using Docker Compose: +```bash +cd backend +docker-compose up -d ``` -**Note**: Make sure to set a secure `BASE62_SALT` value in production. +This will start all services. See [Docker Services](#docker-services) section for details. ## Running the Application -### Development Mode +### Docker Compose (Recommended) + +All services are managed via Docker Compose: -Run the application directly: ```bash -go run main.go +cd backend +docker-compose up -d # Start all services +docker-compose logs -f app # View app logs +docker-compose down # Stop all services ``` -The server will start on `http://localhost:8080` (or the port specified in your `.env` file). +The API will be available at `http://localhost:8888` (via Nginx load balancer). -### Using Make +### Development Mode (Local) -The project includes a Makefile with useful commands: +If you want to run the application locally outside Docker: -```bash -# Run tests -make test +1. Update `.env` to use `localhost` instead of Docker service names: +```env +CASSANDRA_HOST=localhost +REDIS_HOST=localhost +OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317 +``` -# Generate test coverage report -make coverage +2. Start only the infrastructure services: +```bash +cd backend +docker-compose up -d cassandra redis grafana +``` -# Generate Swagger documentation -make swagger +3. Run the application: +```bash +cd backend +go run cmd/app/main.go +``` -# Generate migrations (requires migrate tool) -make generate-migration NAME=your_migration_name +The server will start on `http://localhost:8080` (or the port specified in your `.env` file). -# Generate all (swagger + mocks) -make generate -``` ## API Endpoints @@ -177,51 +219,54 @@ Retrieve the original URL from a short code. In development mode, Swagger documentation is available at: ``` -http://localhost:8080/swagger/index.html +http://localhost:8888/swagger/index.html ``` -## Testing - -Run the full test suite: -```bash -go test ./... -``` -Or use the Makefile: -```bash -make test -``` +## Testing -Generate test coverage: ```bash -make coverage +cd backend +make test # Run tests +make coverage # Generate coverage report ``` -The tests use isolated test databases that are automatically created and cleaned up for each test. +Tests use isolated test databases that are automatically created and cleaned up. ## Project Structure ``` lnk/ -├── domain/ # Domain layer -│ └── entities/ -│ ├── helpers/ # URL encoding/decoding utilities -│ └── usecases/ # Business logic -├── gateways/ # Gateway layer -│ ├── gocql/ # Cassandra integration -│ │ ├── migrations/ # Database migrations -│ │ └── repositories/ # Data access layer -│ └── http/ # HTTP handlers and router -├── extensions/ # Infrastructure extensions -│ ├── config/ # Configuration management -│ ├── logger/ # Logging utilities -│ ├── redis/ # Redis client -│ └── gocqltesting/ # Testing utilities -├── docs/ # Swagger documentation -├── main.go # Application entry point -├── docker-compose.yml # Docker services configuration -├── Makefile # Build automation -└── README.md # This file +├── backend/ +│ ├── cmd/ +│ │ ├── app/ +│ │ │ └── main.go # Application entry point +│ │ └── migrator/ +│ │ └── main.go # Migration service +│ ├── domain/ # Domain layer +│ │ └── entities/ +│ │ ├── helpers/ # URL encoding/decoding utilities +│ │ └── usecases/ # Business logic +│ ├── gateways/ # Gateway layer +│ │ ├── gocql/ # Cassandra integration +│ │ │ ├── migrations/ # Database migrations +│ │ │ └── repositories/ # Data access layer +│ │ └── http/ # HTTP handlers and router +│ ├── extensions/ # Infrastructure extensions +│ │ ├── config/ # Configuration management +│ │ ├── logger/ # Logging utilities +│ │ ├── redis/ # Redis client +│ │ └── opentelemetry/ # OpenTelemetry setup +│ ├── nginx/ +│ │ └── nginx.conf # Nginx load balancer config +│ ├── haproxy/ +│ │ └── haproxy.cfg # HAProxy Cassandra LB config +│ ├── docker-compose.yml # Docker services configuration +│ ├── Dockerfile # Multi-stage build (migrator + app) +│ ├── Makefile # Build automation +│ └── .env # Environment configuration +├── frontend/ # Next.js frontend application +└── Readme.md # This file ``` ## Database Schema @@ -241,6 +286,7 @@ CREATE TABLE urls ( This design ensures fast lookups when retrieving URLs by their short code. + ## Frontend The frontend is a modern Next.js application that provides a user-friendly interface for the URL shortener service. @@ -281,7 +327,7 @@ npm run generate:api bun run generate:api ``` -**Note**: Make sure the backend is running and Swagger documentation is available at `http://localhost:8080/swagger/doc.json` before generating the API client. +**Note**: Make sure the backend is running and Swagger documentation is available at `http://localhost:8888/swagger/doc.json` before generating the API client. ### Running the Frontend @@ -295,6 +341,8 @@ bun run dev The frontend will start on `http://localhost:3000` (default Next.js port). +**Important**: Update the API base URL in `frontend/src/api/undici-instance.ts` to point to `http://localhost:8888` (Nginx load balancer). + #### Production Build ```bash @@ -315,19 +363,6 @@ bun run start - `npm run format` / `bun run format`: Format code - `npm run generate:api` / `bun run generate:api`: Generate API client from Swagger -### Frontend Technologies - -- **Next.js 16**: React framework with App Router -- **React 19**: UI library -- **TypeScript**: Type safety -- **Tailwind CSS**: Utility-first CSS framework -- **shadcn/ui**: High-quality component library -- **Orval**: OpenAPI client generator -- **Biome**: Fast linter and formatter -- **React Hook Form**: Form management -- **Sonner**: Toast notifications -- **Lucide React**: Icon library - ### Frontend Project Structure ``` @@ -361,8 +396,11 @@ frontend/ - **Cassandra (gocql)**: Database for URL storage - **Redis**: Counter management for URL generation - **Zap**: Structured logging +- **OpenTelemetry**: Distributed tracing - **Swagger/OpenAPI**: API documentation - **Docker Compose**: Local development environment +- **Nginx**: Load balancer +- **HAProxy**: Cassandra load balancer - **Testify**: Testing framework ### Frontend @@ -373,58 +411,111 @@ frontend/ - **shadcn/ui**: Component library - **Orval**: API client generator -## Configuration +## Development -The application uses environment variables for configuration. All configuration options can be set in a `.env` file or as environment variables. +### Makefile Commands -### Required Environment Variables +```bash +cd backend -- `BASE62_SALT`: Secret salt for URL encoding -- `CASSANDRA_HOST`: Cassandra host address -- `CASSANDRA_PORT`: Cassandra port -- `CASSANDRA_USERNAME`: Cassandra username -- `CASSANDRA_PASSWORD`: Cassandra password -- `CASSANDRA_KEYSPACE`: Cassandra keyspace name -- `REDIS_HOST`: Redis host address -- `REDIS_PORT`: Redis port -- `REDIS_PASSWORD`: Redis password -- `REDIS_DB`: Redis database number -- `COUNTER_KEY`: Redis key for URL counter -- `COUNTER_START_VAL`: Starting value for URL counter +# Testing +make test # Run tests +make coverage # Generate test coverage report -### Optional Environment Variables +# Documentation +make swagger # Generate Swagger documentation -- `ENV`: Environment name (default: `development`) -- `PORT`: Server port (default: `8080`) -- `GIN_MODE`: Gin mode (default: `debug`) -- `LOG_LEVEL`: Logging level (default: `info`) -- `CASSANDRA_AUTO_MIGRATE`: Auto-run migrations (default: `false`) +# Migrations +make generate-migration NAME=your_migration_name # Create new migration +# Migrations auto-run via migrator service when using docker-compose -## Development +# Code Generation +make generate # Generate all (swagger + mocks) +``` + +### Database Migrations -### Adding a New Migration +Migrations run automatically via the `migrator` service before the app starts. To add a new migration: ```bash +cd backend make generate-migration NAME=your_migration_name ``` -This will create up and down migration files in `gateways/gocql/migrations/`. +This creates migration files in `gateways/gocql/migrations/` that will run automatically on next `docker-compose up`. + +## Docker Services + +### Service Overview + +| Service | Port | Description | +|---------|------|-------------| +| **Nginx** | 8888 | Load balancer (frontend access point) | +| **App** | 8080 | Application service (4 replicas, internal) | +| **Migrator** | - | Runs database migrations, then exits | +| **HAProxy** | 9042 | Cassandra load balancer | +| **Cassandra** | - | Primary database (accessed via HAProxy) | +| **Redis** | 6379 | Counter management and caching | +| **Grafana** | 8081, 4317 | Observability UI (8081) and OTLP gRPC (4317) | -### Generating Swagger Documentation +### Service Startup Order -After updating API endpoints with Swagger annotations: +Services start in the following order with health checks: +1. **Cassandra** → waits until healthy (`nodetool status`) +2. **HAProxy** → waits for Cassandra, checks with `pgrep haproxy` +3. **Redis** → checks with `redis-cli ping` +4. **Grafana** → observability stack +5. **Migrator** → waits for Cassandra, runs migrations, exits +6. **App** → waits for migrator completion, 4 replicas, HTTP health check +7. **Nginx** → waits for app service + +Check service status: ```bash -make swagger +docker-compose ps ``` -### Code Generation +### Observability + +OpenTelemetry traces are automatically exported to Grafana: +- **OTLP Endpoint**: `grafana:4317` (gRPC) when running in Docker +- **Grafana UI**: `http://localhost:8081` (admin/admin) +- **Service Name**: `lnk-backend` (configurable via `SERVICE_NAME` env var) + +## Troubleshooting -Generate mocks and documentation: +### Services not starting +Check service logs: ```bash -make generate +docker-compose logs -f [service-name] ``` +Check service status: +```bash +docker-compose ps +``` + +### Migrations failing + +Check migrator logs: +```bash +docker-compose logs migrator +``` + +Ensure Cassandra is healthy: +```bash +docker-compose exec cassandra nodetool status +``` + +### Can't connect to services + +- Verify service names in `.env` match Docker service names +- Check that services are on the same Docker network (`lnk-network`) +- Verify ports are not already in use +### Frontend can't reach backend +- Ensure backend is running: `http://localhost:8888/health` +- Check CORS configuration in `gateways/http/middleware/middleware.go` +- Verify API base URL in frontend configuration diff --git a/backend/Makefile b/backend/Makefile index 0d246a4..02409bf 100644 --- a/backend/Makefile +++ b/backend/Makefile @@ -37,9 +37,4 @@ run-migrator: .PHONY: run-app run-app: - @docker compose run app - -# compose up with 3 cassandra nodes and 4 app nodes -.PHONY: compose-up -compose-up: - @docker compose up -d --scale cassandra=3 --scale app=4 \ No newline at end of file + @docker compose run app \ No newline at end of file diff --git a/backend/gateways/http/middleware/middleware.go b/backend/gateways/http/middleware/middleware.go index a6c2331..7570d94 100644 --- a/backend/gateways/http/middleware/middleware.go +++ b/backend/gateways/http/middleware/middleware.go @@ -46,12 +46,8 @@ func Recovery(logger *zap.Logger) gin.HandlerFunc { func CORS() gin.HandlerFunc { return func(c *gin.Context) { - origin := c.GetHeader("Origin") - if origin == "" { - origin = "*" - } - - c.Writer.Header().Set("Access-Control-Allow-Origin", origin) + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH") From 16b4fe83510ec33bd91a34bea23a945309190e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Alves?= Date: Wed, 19 Nov 2025 15:05:39 -0300 Subject: [PATCH 6/6] refactor: unify logger usage across main application and migrator service --- backend/cmd/app/main.go | 73 ++++++++++++++++++------------------ backend/cmd/migrator/main.go | 16 ++++---- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/backend/cmd/app/main.go b/backend/cmd/app/main.go index 1658cfe..61b061c 100644 --- a/backend/cmd/app/main.go +++ b/backend/cmd/app/main.go @@ -25,50 +25,49 @@ import ( ) func main() { - cfg, logger := setupConfigAndLogger() + cfg, appLogger := setupConfigAndLogger() ctx, cancel := context.WithCancel(context.Background()) defer cancel() shutdownOTel, err := setupOTelSDK(ctx, cfg) if err != nil { - logger.Fatal("Failed to setup OpenTelemetry", zap.Error(err)) + appLogger.Fatal("Failed to setup OpenTelemetry", zap.Error(err)) } defer func() { - if err := shutdownOTel(ctx); err != nil { - logger.Error("Failed to shutdown OpenTelemetry", zap.Error(err)) + if shutdownErr := shutdownOTel(ctx); shutdownErr != nil { + appLogger.Error("Failed to shutdown OpenTelemetry", zap.Error(shutdownErr)) } }() - session, err := setupDatabase(cfg, logger) + session, err := setupDatabase(cfg, appLogger) if err != nil { - log.Fatalf("Failed to setup database: %v", err) + appLogger.Fatal("Failed to setup database", zap.Error(err)) } defer session.Close() - redisClient, err := setupRedis(ctx, cfg, logger) + redisClient, err := setupRedis(ctx, cfg, appLogger) if err != nil { - log.Fatalf("Failed to setup Redis: %v", err) + appLogger.Fatal("Failed to setup Redis", zap.Error(err)) } defer func() { - err := redisClient.Close() - if err != nil { - logger.Error("Failed to close Redis client", zap.Error(err)) + if closeErr := redisClient.Close(); closeErr != nil { + appLogger.Error("Failed to close Redis client", zap.Error(closeErr)) } }() - err = initializeCounter(ctx, redisClient, cfg, logger) + err = initializeCounter(ctx, redisClient, cfg, appLogger) if err != nil { - log.Fatalf("Failed to initialize counter: %v", err) + appLogger.Fatal("Failed to initialize counter", zap.Error(err)) } - useCase := createUseCase(cfg, logger, session, redisClient) - server, err := createAndStartServer(cfg, logger, useCase) + useCase := createUseCase(cfg, appLogger, session, redisClient) + server, err := createAndStartServer(cfg, appLogger, useCase) if err != nil { - log.Fatalf("Failed to create and start server: %v", err) + appLogger.Fatal("Failed to create and start server", zap.Error(err)) } - shutdownServer(ctx, logger, server) + shutdownServer(ctx, appLogger, server) } func setupOTelSDK(ctx context.Context, cfg *config.Config) (func(context.Context) error, error) { @@ -85,18 +84,18 @@ func setupConfigAndLogger() (*config.Config, *zap.Logger) { log.Fatalf("Failed to load config: %v", err) } - logger, err := logger.NewLogger(cfg.Logger) + appLogger, err := logger.NewLogger(cfg.Logger) if err != nil { log.Fatalf("Failed to create logger: %v", err) } - logger.Info("Starting application") + appLogger.Info("Starting application") - return cfg, logger + return cfg, appLogger } -func setupDatabase(cfg *config.Config, logger *zap.Logger) (*gocql.Session, error) { - session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, logger, false) +func setupDatabase(cfg *config.Config, appLogger *zap.Logger) (*gocql.Session, error) { + session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, appLogger, false) if err != nil { return nil, fmt.Errorf("failed to setup database: %w", err) } @@ -104,8 +103,8 @@ func setupDatabase(cfg *config.Config, logger *zap.Logger) (*gocql.Session, erro return session, nil } -func setupRedis(ctx context.Context, cfg *config.Config, logger *zap.Logger) (*redis.Client, error) { - redisClient, err := redisPackage.SetupRedis(ctx, &cfg.Redis, logger) +func setupRedis(ctx context.Context, cfg *config.Config, appLogger *zap.Logger) (*redis.Client, error) { + redisClient, err := redisPackage.SetupRedis(ctx, &cfg.Redis, appLogger) if err != nil { return nil, fmt.Errorf("failed to setup Redis: %w", err) } @@ -113,8 +112,8 @@ func setupRedis(ctx context.Context, cfg *config.Config, logger *zap.Logger) (*r return redisClient, nil } -func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *config.Config, logger *zap.Logger) error { - _, err := redisPackage.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, logger) +func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *config.Config, appLogger *zap.Logger) error { + _, err := redisPackage.SetInitialCounterValue(ctx, redisClient, &cfg.Redis, appLogger) if err != nil { return fmt.Errorf("failed to set initial counter: %w", err) } @@ -122,12 +121,12 @@ func initializeCounter(ctx context.Context, redisClient *redis.Client, cfg *conf return nil } -func createUseCase(cfg *config.Config, logger *zap.Logger, session *gocql.Session, redisClient *redis.Client) *usecases.UseCase { - repository := repositories.NewRepository(logger, session) +func createUseCase(cfg *config.Config, appLogger *zap.Logger, session *gocql.Session, redisClient *redis.Client) *usecases.UseCase { + repository := repositories.NewRepository(appLogger, session) redisAdapter := redisPackage.NewRedisAdapter(redisClient) return usecases.NewUseCase(usecases.NewUseCaseParams{ - Logger: logger, + Logger: appLogger, Repository: repository, Redis: redisAdapter, Salt: cfg.App.Base62Salt, @@ -135,17 +134,17 @@ func createUseCase(cfg *config.Config, logger *zap.Logger, session *gocql.Sessio }) } -func createAndStartServer(cfg *config.Config, logger *zap.Logger, useCase *usecases.UseCase) (*httpServer.Server, error) { - httpHandlers := handlers.NewHandlers(logger, useCase) +func createAndStartServer(cfg *config.Config, appLogger *zap.Logger, useCase *usecases.UseCase) (*httpServer.Server, error) { + httpHandlers := handlers.NewHandlers(appLogger, useCase) router := httpServer.NewRouter(httpServer.RouterConfig{ - Logger: logger, + Logger: appLogger, GinMode: cfg.App.GinMode, Env: cfg.App.ENV, Handlers: httpHandlers, }) - server := httpServer.NewServer(logger, cfg.App.Port, router) + server := httpServer.NewServer(appLogger, cfg.App.Port, router) err := server.Start() if err != nil { @@ -155,12 +154,12 @@ func createAndStartServer(cfg *config.Config, logger *zap.Logger, useCase *useca return server, nil } -func shutdownServer(ctx context.Context, logger *zap.Logger, server *httpServer.Server) { +func shutdownServer(ctx context.Context, appLogger *zap.Logger, server *httpServer.Server) { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) <-sigChan - logger.Info("Received shutdown signal") + appLogger.Info("Received shutdown signal") const shutdownTimeout = 10 * time.Second @@ -169,8 +168,8 @@ func shutdownServer(ctx context.Context, logger *zap.Logger, server *httpServer. err := server.Shutdown(shutdownCtx) if err != nil { - logger.Error("Error during server shutdown", zap.Error(err)) + appLogger.Error("Error during server shutdown", zap.Error(err)) } - logger.Info("Application stopped") + appLogger.Info("Application stopped") } diff --git a/backend/cmd/migrator/main.go b/backend/cmd/migrator/main.go index 5b7c7de..d3103ac 100644 --- a/backend/cmd/migrator/main.go +++ b/backend/cmd/migrator/main.go @@ -13,16 +13,16 @@ import ( ) func main() { - cfg, logger := setupConfigAndLogger() + cfg, appLogger := setupConfigAndLogger() - session, err := setupDatabase(cfg, logger) + session, err := setupDatabase(cfg, appLogger) if err != nil { log.Fatalf("Failed to setup database: %v", err) } defer session.Close() - logger.Info("Migrations completed successfully") + appLogger.Info("Migrations completed successfully") } func setupConfigAndLogger() (*config.Config, *zap.Logger) { @@ -31,18 +31,18 @@ func setupConfigAndLogger() (*config.Config, *zap.Logger) { log.Fatalf("Failed to load config: %v", err) } - logger, err := logger.NewLogger(cfg.Logger) + appLogger, err := logger.NewLogger(cfg.Logger) if err != nil { log.Fatalf("Failed to create logger: %v", err) } - logger.Info("Starting application") + appLogger.Info("Starting application") - return cfg, logger + return cfg, appLogger } -func setupDatabase(cfg *config.Config, logger *zap.Logger) (*gocql.Session, error) { - session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, logger, cfg.Gocql.AutoMigrate) +func setupDatabase(cfg *config.Config, appLogger *zap.Logger) (*gocql.Session, error) { + session, err := gocqlPackage.SetupDatabase(&cfg.Gocql, appLogger, cfg.Gocql.AutoMigrate) if err != nil { return nil, fmt.Errorf("failed to setup database: %w", err) }