mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 02:51:34 +00:00
refactor(api): Consolidate server setup
This commit refactors the API server startup logic in `cmd/api/main.go` to simplify the application's architecture. Key changes: - Consolidates the three separate HTTP servers (GraphQL API, GraphQL Playground, and Prometheus metrics) into a single `http.Server` instance. - Uses a single `http.ServeMux` to route requests to the appropriate handlers on distinct paths (`/query`, `/playground`, `/metrics`). - Removes the now-redundant `PlaygroundPort` from the application's configuration. This change simplifies the server startup and shutdown logic, reduces resource usage, and makes the application's entry point cleaner and easier to maintain.
This commit is contained in:
parent
488bc141de
commit
1bb3e23c47
105
cmd/api/main.go
105
cmd/api/main.go
@ -112,84 +112,83 @@ func main() {
|
|||||||
log.Fatal(err, "Failed to create sentiment provider")
|
log.Fatal(err, "Failed to create sentiment provider")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create platform components
|
||||||
|
jwtManager := auth.NewJWTManager()
|
||||||
|
|
||||||
// Create application services
|
// Create application services
|
||||||
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
||||||
|
|
||||||
|
// Create application dependencies
|
||||||
|
deps := app.Dependencies{
|
||||||
|
WorkRepo: repos.Work,
|
||||||
|
UserRepo: repos.User,
|
||||||
|
AuthorRepo: repos.Author,
|
||||||
|
TranslationRepo: repos.Translation,
|
||||||
|
CommentRepo: repos.Comment,
|
||||||
|
LikeRepo: repos.Like,
|
||||||
|
BookmarkRepo: repos.Bookmark,
|
||||||
|
CollectionRepo: repos.Collection,
|
||||||
|
TagRepo: repos.Tag,
|
||||||
|
CategoryRepo: repos.Category,
|
||||||
|
BookRepo: repos.Book,
|
||||||
|
PublisherRepo: repos.Publisher,
|
||||||
|
SourceRepo: repos.Source,
|
||||||
|
CopyrightRepo: repos.Copyright,
|
||||||
|
MonetizationRepo: repos.Monetization,
|
||||||
|
AnalyticsRepo: repos.Analytics,
|
||||||
|
AuthRepo: repos.Auth,
|
||||||
|
LocalizationRepo: repos.Localization,
|
||||||
|
SearchClient: searchClient,
|
||||||
|
AnalyticsService: analyticsService,
|
||||||
|
JWTManager: jwtManager,
|
||||||
|
}
|
||||||
|
|
||||||
// Create application
|
// Create application
|
||||||
application := app.NewApplication(repos, searchClient, analyticsService)
|
application := app.NewApplication(deps)
|
||||||
|
|
||||||
// Create GraphQL server
|
// Create GraphQL server
|
||||||
resolver := &graph.Resolver{
|
resolver := &graph.Resolver{
|
||||||
App: application,
|
App: application,
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtManager := auth.NewJWTManager()
|
// Create handlers
|
||||||
srv := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
apiHandler := NewServerWithAuth(resolver, jwtManager, metrics, obsLogger)
|
||||||
graphQLServer := &http.Server{
|
playgroundHandler := playground.Handler("GraphQL Playground", "/query")
|
||||||
|
metricsHandler := observability.PrometheusHandler(reg)
|
||||||
|
|
||||||
|
// Consolidate handlers into a single mux
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.Handle("/query", apiHandler)
|
||||||
|
mux.Handle("/playground", playgroundHandler)
|
||||||
|
mux.Handle("/metrics", metricsHandler)
|
||||||
|
|
||||||
|
// Create a single HTTP server
|
||||||
|
mainServer := &http.Server{
|
||||||
Addr: config.Cfg.ServerPort,
|
Addr: config.Cfg.ServerPort,
|
||||||
Handler: srv,
|
Handler: mux,
|
||||||
}
|
}
|
||||||
log.Info(fmt.Sprintf("GraphQL server created successfully on port %s", config.Cfg.ServerPort))
|
log.Info(fmt.Sprintf("API server listening on port %s", config.Cfg.ServerPort))
|
||||||
|
|
||||||
// Create GraphQL playground
|
// Start the main server in a goroutine
|
||||||
playgroundHandler := playground.Handler("GraphQL", "/query")
|
|
||||||
playgroundServer := &http.Server{
|
|
||||||
Addr: config.Cfg.PlaygroundPort,
|
|
||||||
Handler: playgroundHandler,
|
|
||||||
}
|
|
||||||
log.Info(fmt.Sprintf("GraphQL playground created successfully on port %s", config.Cfg.PlaygroundPort))
|
|
||||||
|
|
||||||
// Create metrics server
|
|
||||||
metricsServer := &http.Server{
|
|
||||||
Addr: ":9090",
|
|
||||||
Handler: observability.PrometheusHandler(reg),
|
|
||||||
}
|
|
||||||
log.Info("Metrics server created successfully on port :9090")
|
|
||||||
|
|
||||||
// Start HTTP servers in goroutines
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Info(fmt.Sprintf("Starting GraphQL server on port %s", config.Cfg.ServerPort))
|
if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
if err := graphQLServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
log.Fatal(err, "Failed to start server")
|
||||||
log.Fatal(err, "Failed to start GraphQL server")
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
// Wait for interrupt signal to gracefully shutdown the server
|
||||||
log.Info(fmt.Sprintf("Starting GraphQL playground on port %s", config.Cfg.PlaygroundPort))
|
|
||||||
if err := playgroundServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatal(err, "Failed to start GraphQL playground")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Info("Starting metrics server on port :9090")
|
|
||||||
if err := metricsServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
||||||
log.Fatal(err, "Failed to start metrics server")
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Wait for interrupt signal to gracefully shutdown the servers
|
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
<-quit
|
<-quit
|
||||||
|
log.Info("Shutting down server...")
|
||||||
log.Info("Shutting down servers...")
|
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := graphQLServer.Shutdown(ctx); err != nil {
|
if err := mainServer.Shutdown(ctx); err != nil {
|
||||||
log.Error(err, "GraphQL server forced to shutdown")
|
log.Error(err, "Server forced to shutdown")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := playgroundServer.Shutdown(ctx); err != nil {
|
log.Info("Server shut down successfully")
|
||||||
log.Error(err, "GraphQL playground forced to shutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := metricsServer.Shutdown(ctx); err != nil {
|
|
||||||
log.Error(err, "Metrics server forced to shutdown")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("All servers shutdown successfully")
|
|
||||||
}
|
}
|
||||||
@ -15,13 +15,41 @@ import (
|
|||||||
"tercul/internal/app/user"
|
"tercul/internal/app/user"
|
||||||
"tercul/internal/app/auth"
|
"tercul/internal/app/auth"
|
||||||
"tercul/internal/app/work"
|
"tercul/internal/app/work"
|
||||||
"tercul/internal/data/sql"
|
"tercul/internal/domain"
|
||||||
|
auth_domain "tercul/internal/domain/auth"
|
||||||
|
localization_domain "tercul/internal/domain/localization"
|
||||||
"tercul/internal/domain/search"
|
"tercul/internal/domain/search"
|
||||||
|
work_domain "tercul/internal/domain/work"
|
||||||
platform_auth "tercul/internal/platform/auth"
|
platform_auth "tercul/internal/platform/auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
import "tercul/internal/app/authz"
|
import "tercul/internal/app/authz"
|
||||||
|
|
||||||
|
// Dependencies holds all external dependencies for the application.
|
||||||
|
type Dependencies struct {
|
||||||
|
WorkRepo work_domain.WorkRepository
|
||||||
|
UserRepo domain.UserRepository
|
||||||
|
AuthorRepo domain.AuthorRepository
|
||||||
|
TranslationRepo domain.TranslationRepository
|
||||||
|
CommentRepo domain.CommentRepository
|
||||||
|
LikeRepo domain.LikeRepository
|
||||||
|
BookmarkRepo domain.BookmarkRepository
|
||||||
|
CollectionRepo domain.CollectionRepository
|
||||||
|
TagRepo domain.TagRepository
|
||||||
|
CategoryRepo domain.CategoryRepository
|
||||||
|
BookRepo domain.BookRepository
|
||||||
|
PublisherRepo domain.PublisherRepository
|
||||||
|
SourceRepo domain.SourceRepository
|
||||||
|
CopyrightRepo domain.CopyrightRepository
|
||||||
|
MonetizationRepo domain.MonetizationRepository
|
||||||
|
AnalyticsRepo analytics.Repository
|
||||||
|
AuthRepo auth_domain.AuthRepository
|
||||||
|
LocalizationRepo localization_domain.LocalizationRepository
|
||||||
|
SearchClient search.SearchClient
|
||||||
|
AnalyticsService analytics.Service
|
||||||
|
JWTManager platform_auth.JWTManagement
|
||||||
|
}
|
||||||
|
|
||||||
// Application is a container for all the application-layer services.
|
// Application is a container for all the application-layer services.
|
||||||
type Application struct {
|
type Application struct {
|
||||||
Author *author.Service
|
Author *author.Service
|
||||||
@ -41,22 +69,21 @@ type Application struct {
|
|||||||
Analytics analytics.Service
|
Analytics analytics.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, analyticsService analytics.Service) *Application {
|
func NewApplication(deps Dependencies) *Application {
|
||||||
jwtManager := platform_auth.NewJWTManager()
|
authzService := authz.NewService(deps.WorkRepo, deps.TranslationRepo)
|
||||||
authzService := authz.NewService(repos.Work, repos.Translation)
|
authorService := author.NewService(deps.AuthorRepo)
|
||||||
authorService := author.NewService(repos.Author)
|
bookService := book.NewService(deps.BookRepo, authzService)
|
||||||
bookService := book.NewService(repos.Book, authzService)
|
bookmarkService := bookmark.NewService(deps.BookmarkRepo, deps.AnalyticsService)
|
||||||
bookmarkService := bookmark.NewService(repos.Bookmark, analyticsService)
|
categoryService := category.NewService(deps.CategoryRepo)
|
||||||
categoryService := category.NewService(repos.Category)
|
collectionService := collection.NewService(deps.CollectionRepo)
|
||||||
collectionService := collection.NewService(repos.Collection)
|
commentService := comment.NewService(deps.CommentRepo, authzService, deps.AnalyticsService)
|
||||||
commentService := comment.NewService(repos.Comment, authzService, analyticsService)
|
likeService := like.NewService(deps.LikeRepo, deps.AnalyticsService)
|
||||||
likeService := like.NewService(repos.Like, analyticsService)
|
tagService := tag.NewService(deps.TagRepo)
|
||||||
tagService := tag.NewService(repos.Tag)
|
translationService := translation.NewService(deps.TranslationRepo, authzService)
|
||||||
translationService := translation.NewService(repos.Translation, authzService)
|
userService := user.NewService(deps.UserRepo, authzService)
|
||||||
userService := user.NewService(repos.User, authzService)
|
localizationService := localization.NewService(deps.LocalizationRepo)
|
||||||
localizationService := localization.NewService(repos.Localization)
|
authService := auth.NewService(deps.UserRepo, deps.JWTManager)
|
||||||
authService := auth.NewService(repos.User, jwtManager)
|
workService := work.NewService(deps.WorkRepo, deps.SearchClient, authzService)
|
||||||
workService := work.NewService(repos.Work, searchClient, authzService)
|
|
||||||
|
|
||||||
return &Application{
|
return &Application{
|
||||||
Author: authorService,
|
Author: authorService,
|
||||||
@ -73,6 +100,6 @@ func NewApplication(repos *sql.Repositories, searchClient search.SearchClient, a
|
|||||||
Auth: authService,
|
Auth: authService,
|
||||||
Authz: authzService,
|
Authz: authzService,
|
||||||
Work: workService,
|
Work: workService,
|
||||||
Analytics: analyticsService,
|
Analytics: deps.AnalyticsService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,7 +31,6 @@ type Config struct {
|
|||||||
// Application configuration
|
// Application configuration
|
||||||
Port string
|
Port string
|
||||||
ServerPort string
|
ServerPort string
|
||||||
PlaygroundPort string
|
|
||||||
Environment string
|
Environment string
|
||||||
LogLevel string
|
LogLevel string
|
||||||
|
|
||||||
@ -84,7 +83,6 @@ func LoadConfig() {
|
|||||||
// Application configuration
|
// Application configuration
|
||||||
Port: getEnv("PORT", "8080"),
|
Port: getEnv("PORT", "8080"),
|
||||||
ServerPort: getEnv("SERVER_PORT", "8080"),
|
ServerPort: getEnv("SERVER_PORT", "8080"),
|
||||||
PlaygroundPort: getEnv("PLAYGROUND_PORT", "8081"),
|
|
||||||
Environment: getEnv("ENVIRONMENT", "development"),
|
Environment: getEnv("ENVIRONMENT", "development"),
|
||||||
LogLevel: getEnv("LOG_LEVEL", "info"),
|
LogLevel: getEnv("LOG_LEVEL", "info"),
|
||||||
|
|
||||||
|
|||||||
@ -163,7 +163,32 @@ func (s *IntegrationTestSuite) SetupSuite(config *TestConfig) {
|
|||||||
s.T().Fatalf("Failed to create sentiment provider: %v", err)
|
s.T().Fatalf("Failed to create sentiment provider: %v", err)
|
||||||
}
|
}
|
||||||
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
analyticsService := analytics.NewService(repos.Analytics, analysisRepo, repos.Translation, repos.Work, sentimentProvider)
|
||||||
s.App = app.NewApplication(repos, searchClient, analyticsService)
|
jwtManager := platform_auth.NewJWTManager()
|
||||||
|
|
||||||
|
deps := app.Dependencies{
|
||||||
|
WorkRepo: repos.Work,
|
||||||
|
UserRepo: repos.User,
|
||||||
|
AuthorRepo: repos.Author,
|
||||||
|
TranslationRepo: repos.Translation,
|
||||||
|
CommentRepo: repos.Comment,
|
||||||
|
LikeRepo: repos.Like,
|
||||||
|
BookmarkRepo: repos.Bookmark,
|
||||||
|
CollectionRepo: repos.Collection,
|
||||||
|
TagRepo: repos.Tag,
|
||||||
|
CategoryRepo: repos.Category,
|
||||||
|
BookRepo: repos.Book,
|
||||||
|
PublisherRepo: repos.Publisher,
|
||||||
|
SourceRepo: repos.Source,
|
||||||
|
CopyrightRepo: repos.Copyright,
|
||||||
|
MonetizationRepo: repos.Monetization,
|
||||||
|
AnalyticsRepo: repos.Analytics,
|
||||||
|
AuthRepo: repos.Auth,
|
||||||
|
LocalizationRepo: repos.Localization,
|
||||||
|
SearchClient: searchClient,
|
||||||
|
AnalyticsService: analyticsService,
|
||||||
|
JWTManager: jwtManager,
|
||||||
|
}
|
||||||
|
s.App = app.NewApplication(deps)
|
||||||
|
|
||||||
// Create a default admin user for tests
|
// Create a default admin user for tests
|
||||||
adminUser := &domain.User{
|
adminUser := &domain.User{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user