monorepo/cloud/maplefile-backend/app/app.go

139 lines
4.5 KiB
Go

package app
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/gocql/gocql"
"go.uber.org/zap"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/scheduler"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/scheduler/tasks"
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/storage/database/cassandradb"
)
// Application represents the main application using Wire DI
type Application struct {
config *config.Config
httpServer *http.WireServer
logger *zap.Logger
migrator *cassandradb.Migrator
scheduler *scheduler.Scheduler
ipAnonymizationTask *tasks.IPAnonymizationTask
dbSession *gocql.Session
}
// ProvideApplication creates the application instance for Wire
func ProvideApplication(
cfg *config.Config,
httpServer *http.WireServer,
logger *zap.Logger,
migrator *cassandradb.Migrator,
sched *scheduler.Scheduler,
ipAnonymizationTask *tasks.IPAnonymizationTask,
dbSession *gocql.Session,
) *Application {
return &Application{
config: cfg,
httpServer: httpServer,
logger: logger,
migrator: migrator,
scheduler: sched,
ipAnonymizationTask: ipAnonymizationTask,
dbSession: dbSession,
}
}
// Start starts the application
func (app *Application) Start() error {
app.logger.Info("🚀 MapleFile Backend Starting (Wire DI)",
zap.String("version", app.config.App.Version),
zap.String("environment", app.config.App.Environment),
zap.String("di_framework", "Google Wire"))
// Run database migrations automatically on startup if enabled
if app.config.Database.AutoMigrate {
app.logger.Info("Auto-migration enabled, running database migrations...")
if err := app.migrator.Up(); err != nil {
app.logger.Error("Failed to run database migrations", zap.Error(err))
return fmt.Errorf("migration failed: %w", err)
}
app.logger.Info("✅ Database migrations completed successfully")
// Wait for schema agreement across all Cassandra nodes
// This ensures all nodes have the new schema before we start accepting requests
app.logger.Info("⏳ Waiting for Cassandra schema agreement...")
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
if err := app.dbSession.AwaitSchemaAgreement(ctx); err != nil {
app.logger.Warn("Schema agreement wait failed, continuing anyway",
zap.Error(err),
zap.String("note", "This may cause transient errors on first requests"))
} else {
app.logger.Info("✅ Cassandra schema agreement reached")
}
} else {
app.logger.Info("Auto-migration disabled (DATABASE_AUTO_MIGRATE=false), skipping migrations")
}
// Register scheduled tasks
app.logger.Info("Registering scheduled tasks...")
if err := app.scheduler.RegisterTask(app.ipAnonymizationTask); err != nil {
app.logger.Error("Failed to register IP anonymization task", zap.Error(err))
return fmt.Errorf("task registration failed: %w", err)
}
// Start scheduler
if err := app.scheduler.Start(); err != nil {
app.logger.Error("Failed to start scheduler", zap.Error(err))
return fmt.Errorf("scheduler startup failed: %w", err)
}
// Start HTTP server in goroutine
errChan := make(chan error, 1)
go func() {
if err := app.httpServer.Start(); err != nil {
errChan <- err
}
}()
// Wait for interrupt signal or server error
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
select {
case err := <-errChan:
app.logger.Error("HTTP server failed", zap.Error(err))
return fmt.Errorf("server startup failed: %w", err)
case sig := <-quit:
app.logger.Info("Received shutdown signal", zap.String("signal", sig.String()))
}
app.logger.Info("👋 MapleFile Backend Shutting Down")
// Graceful shutdown with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Stop scheduler first
app.logger.Info("Stopping scheduler...")
if err := app.scheduler.Stop(); err != nil {
app.logger.Error("Scheduler shutdown error", zap.Error(err))
// Continue with shutdown even if scheduler fails
}
// Stop HTTP server
if err := app.httpServer.Shutdown(ctx); err != nil {
app.logger.Error("Server shutdown error", zap.Error(err))
return fmt.Errorf("server shutdown failed: %w", err)
}
app.logger.Info("✅ MapleFile Backend Stopped Successfully")
return nil
}