// Package api provides gRPC server implementation for Audit Service. package api import ( "context" "fmt" "net" "time" auditv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/audit/v1" "git.dcentral.systems/toolz/goplt/pkg/config" "git.dcentral.systems/toolz/goplt/pkg/logger" "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/health" "google.golang.org/grpc/health/grpc_health_v1" "google.golang.org/grpc/reflection" ) // GRPCServer wraps the gRPC server with lifecycle management. type GRPCServer struct { server *grpc.Server listener net.Listener config config.ConfigProvider logger logger.Logger port int } // NewGRPCServer creates a new gRPC server for the Audit Service. func NewGRPCServer( auditService *Server, cfg config.ConfigProvider, log logger.Logger, ) (*GRPCServer, error) { // Get port from config port := cfg.GetInt("services.audit.port") if port == 0 { port = 8084 // Default port for audit service } // Create listener addr := fmt.Sprintf("0.0.0.0:%d", port) listener, err := net.Listen("tcp", addr) if err != nil { return nil, fmt.Errorf("failed to listen on %s: %w", addr, err) } // Create gRPC server grpcServer := grpc.NewServer() // Register audit service auditv1.RegisterAuditServiceServer(grpcServer, auditService) // Register health service healthServer := health.NewServer() grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) healthServer.SetServingStatus("audit.v1.AuditService", grpc_health_v1.HealthCheckResponse_SERVING) // Register reflection for grpcurl reflection.Register(grpcServer) return &GRPCServer{ server: grpcServer, listener: listener, config: cfg, logger: log, port: port, }, nil } // Start starts the gRPC server. func (s *GRPCServer) Start() error { s.logger.Info("Starting Audit Service gRPC server", zap.Int("port", s.port), zap.String("addr", s.listener.Addr().String()), ) // Start server in a goroutine errChan := make(chan error, 1) go func() { if err := s.server.Serve(s.listener); err != nil { errChan <- err } }() // Wait a bit to check for immediate errors select { case err := <-errChan: return fmt.Errorf("gRPC server failed to start: %w", err) case <-time.After(100 * time.Millisecond): s.logger.Info("Audit Service gRPC server started successfully", zap.Int("port", s.port), ) return nil } } // Stop gracefully stops the gRPC server. func (s *GRPCServer) Stop(ctx context.Context) error { s.logger.Info("Stopping Audit Service gRPC server") // Create a channel for graceful stop stopped := make(chan struct{}) go func() { s.server.GracefulStop() close(stopped) }() // Wait for graceful stop or timeout select { case <-stopped: s.logger.Info("Audit Service gRPC server stopped gracefully") return nil case <-ctx.Done(): s.logger.Warn("Audit Service gRPC server stop timeout, forcing stop") s.server.Stop() return ctx.Err() } } // Port returns the port the server is listening on. func (s *GRPCServer) Port() int { return s.port }