mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 00:31:35 +00:00
This commit implements a robust, production-ready analytics system using an event-driven architecture with Redis and `asynq`. Key changes: - Event-Driven Architecture: Instead of synchronous database updates, analytics events (e.g., views, likes, comments) are now published to a Redis queue. This improves API response times and decouples the analytics system from the main application flow. - Background Worker: A new worker process (`cmd/worker`) has been created to consume events from the queue and update the analytics counters in the database. - View Counting: Implemented the missing view counting feature for both works and translations. - New Analytics Query: Added a `popularTranslations` GraphQL query to demonstrate how to use the collected analytics data. - Testing: Added unit tests for the new event publisher and integration tests for the analytics worker. Known Issue: The integration tests for the analytics worker (`AnalyticsWorkerSuite`) and the GraphQL API (`GraphQLIntegrationSuite`) are currently failing due to the lack of a Redis service in the test environment. The tests are written and are expected to pass in an environment where Redis is available on `localhost:6379`, as configured in the CI pipeline.
73 lines
2.2 KiB
Go
73 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"tercul/internal/app"
|
|
app_analytics "tercul/internal/app/analytics"
|
|
analytics_job "tercul/internal/jobs/analytics"
|
|
"tercul/internal/platform/config"
|
|
app_log "tercul/internal/platform/log"
|
|
|
|
"github.com/hibiken/asynq"
|
|
)
|
|
|
|
func main() {
|
|
// Load configuration from environment variables
|
|
config.LoadConfig()
|
|
|
|
// Initialize structured logger
|
|
app_log.SetDefaultLevel(app_log.InfoLevel)
|
|
app_log.LogInfo("Starting Tercul worker")
|
|
|
|
// Build application components
|
|
appBuilder := app.NewApplicationBuilder()
|
|
if err := appBuilder.Build(); err != nil {
|
|
log.Fatalf("Failed to build application: %v", err)
|
|
}
|
|
defer appBuilder.Close()
|
|
|
|
// Create asynq server
|
|
srv := asynq.NewServer(
|
|
asynq.RedisClientOpt{
|
|
Addr: config.Cfg.RedisAddr,
|
|
Password: config.Cfg.RedisPassword,
|
|
DB: config.Cfg.RedisDB,
|
|
},
|
|
asynq.Config{
|
|
Queues: map[string]int{
|
|
app_analytics.QueueAnalytics: 10, // Process analytics queue with priority 10
|
|
},
|
|
},
|
|
)
|
|
|
|
// Create and register analytics worker
|
|
analyticsWorker := analytics_job.NewWorker(appBuilder.App.AnalyticsService)
|
|
mux := asynq.NewServeMux()
|
|
mux.HandleFunc(string(app_analytics.EventTypeWorkViewed), analyticsWorker.ProcessTask)
|
|
mux.HandleFunc(string(app_analytics.EventTypeWorkLiked), analyticsWorker.ProcessTask)
|
|
mux.HandleFunc(string(app_analytics.EventTypeWorkCommented), analyticsWorker.ProcessTask)
|
|
mux.HandleFunc(string(app_analytics.EventTypeWorkBookmarked), analyticsWorker.ProcessTask)
|
|
mux.HandleFunc(string(app_analytics.EventTypeTranslationViewed), analyticsWorker.ProcessTask)
|
|
mux.HandleFunc(string(app_analytics.EventTypeTranslationLiked), analyticsWorker.ProcessTask)
|
|
mux.HandleFunc(string(app_analytics.EventTypeTranslationCommented), analyticsWorker.ProcessTask)
|
|
|
|
// Start the server
|
|
go func() {
|
|
if err := srv.Run(mux); err != nil {
|
|
log.Fatalf("could not run asynq server: %v", err)
|
|
}
|
|
}()
|
|
|
|
// Wait for interrupt signal to gracefully shutdown the server
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
<-quit
|
|
|
|
log.Println("Shutting down worker server...")
|
|
srv.Shutdown()
|
|
log.Println("Worker server shutdown successfully")
|
|
}
|