mirror of
https://github.com/redis/go-redis.git
synced 2025-07-19 11:43:14 +03:00
refactor: move all push notification logic to root package and remove adapters
Consolidate all push notification handling logic in the root package to eliminate adapters and simplify the architecture. This provides direct access to concrete types without any intermediate layers or type conversions. Key Changes: 1. Moved Core Types to Root Package: - Moved Registry, Processor, VoidProcessor to push_notifications.go - Moved all push notification constants to root package - Removed internal/pushnotif package dependencies - Direct implementation without internal abstractions 2. Eliminated All Adapters: - Removed handlerAdapter that bridged internal and public interfaces - Removed voidProcessorAdapter for void processor functionality - Removed convertInternalToPublicContext conversion functions - Direct usage of concrete types throughout 3. Simplified Architecture: - PushNotificationHandlerContext directly implemented in root package - PushNotificationHandler directly implemented in root package - Registry, Processor, VoidProcessor directly in root package - No intermediate layers or type conversions needed 4. Direct Type Usage: - GetClusterClient() returns *ClusterClient directly - GetSentinelClient() returns *SentinelClient directly - GetRegularClient() returns *Client directly - GetPubSub() returns *PubSub directly - No interface casting or type assertions required 5. Updated All Integration Points: - Updated redis.go to use direct types - Updated pubsub.go to use direct types - Updated sentinel.go to use direct types - Removed all internal/pushnotif imports - Simplified context creation and usage 6. Core Implementation in Root Package: ```go // Direct implementation - no adapters needed type Registry struct { handlers map[string]PushNotificationHandler protected map[string]bool } type Processor struct { registry *Registry } type VoidProcessor struct{} ``` 7. Handler Context with Concrete Types: ```go type PushNotificationHandlerContext interface { GetClusterClient() *ClusterClient // Direct concrete type GetSentinelClient() *SentinelClient // Direct concrete type GetRegularClient() *Client // Direct concrete type GetPubSub() *PubSub // Direct concrete type } ``` 8. Comprehensive Test Suite: - Added push_notifications_test.go with full test coverage - Tests for Registry, Processor, VoidProcessor - Tests for HandlerContext with concrete type access - Tests for all push notification constants - Validates all functionality works correctly 9. Benefits: - Eliminated complex adapter pattern - Removed unnecessary type conversions - Simplified codebase with direct type usage - Better performance without adapter overhead - Cleaner architecture with single source of truth - Enhanced developer experience with direct access 10. Architecture Simplification: Before: Client -> Adapter -> Internal -> Adapter -> Handler After: Client -> Handler (direct) No more: - handlerAdapter bridging interfaces - voidProcessorAdapter for void functionality - convertInternalToPublicContext conversions - Complex type mapping between layers This refactoring provides a much cleaner, simpler architecture where all push notification logic lives in the root package with direct access to concrete Redis client types, eliminating unnecessary complexity while maintaining full functionality and type safety.
This commit is contained in:
187
internal/pushprocessor/processor.go
Normal file
187
internal/pushprocessor/processor.go
Normal file
@ -0,0 +1,187 @@
|
||||
package pushprocessor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9/internal/proto"
|
||||
)
|
||||
|
||||
// Processor handles push notifications with a registry of handlers.
|
||||
type Processor struct {
|
||||
registry *Registry
|
||||
}
|
||||
|
||||
// NewProcessor creates a new push notification processor.
|
||||
func NewProcessor() *Processor {
|
||||
return &Processor{
|
||||
registry: NewRegistry(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetHandler returns the handler for a specific push notification name.
|
||||
// Returns nil if no handler is registered for the given name.
|
||||
func (p *Processor) GetHandler(pushNotificationName string) Handler {
|
||||
return p.registry.GetHandler(pushNotificationName)
|
||||
}
|
||||
|
||||
// RegisterHandler registers a handler for a specific push notification name.
|
||||
// Returns an error if a handler is already registered for this push notification name.
|
||||
// If protected is true, the handler cannot be unregistered.
|
||||
func (p *Processor) RegisterHandler(pushNotificationName string, handler Handler, protected bool) error {
|
||||
return p.registry.RegisterHandler(pushNotificationName, handler, protected)
|
||||
}
|
||||
|
||||
// UnregisterHandler removes a handler for a specific push notification name.
|
||||
// Returns an error if the handler is protected or doesn't exist.
|
||||
func (p *Processor) UnregisterHandler(pushNotificationName string) error {
|
||||
return p.registry.UnregisterHandler(pushNotificationName)
|
||||
}
|
||||
|
||||
// ProcessPendingNotifications checks for and processes any pending push notifications.
|
||||
// The handlerCtx provides context about the client, connection pool, and connection.
|
||||
func (p *Processor) ProcessPendingNotifications(ctx context.Context, handlerCtx HandlerContext, rd *proto.Reader) error {
|
||||
// Check for nil reader
|
||||
if rd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if there are any buffered bytes that might contain push notifications
|
||||
if rd.Buffered() == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Process all available push notifications
|
||||
for {
|
||||
// Peek at the next reply type to see if it's a push notification
|
||||
replyType, err := rd.PeekReplyType()
|
||||
if err != nil {
|
||||
// No more data available or error reading
|
||||
break
|
||||
}
|
||||
|
||||
// Push notifications use RespPush type in RESP3
|
||||
if replyType != proto.RespPush {
|
||||
break
|
||||
}
|
||||
|
||||
notificationName, err := rd.PeekPushNotificationName()
|
||||
if err != nil {
|
||||
// Error reading - continue to next iteration
|
||||
break
|
||||
}
|
||||
|
||||
// Skip notifications that should be handled by other systems
|
||||
if shouldSkipNotification(notificationName) {
|
||||
break
|
||||
}
|
||||
|
||||
// Try to read the push notification
|
||||
reply, err := rd.ReadReply()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read push notification: %w", err)
|
||||
}
|
||||
|
||||
// Convert to slice of interfaces
|
||||
notification, ok := reply.([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle the notification directly
|
||||
if len(notification) > 0 {
|
||||
// Extract the notification type (first element)
|
||||
if notificationType, ok := notification[0].(string); ok {
|
||||
// Skip notifications that should be handled by other systems
|
||||
if shouldSkipNotification(notificationType) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the handler for this notification type
|
||||
if handler := p.registry.GetHandler(notificationType); handler != nil {
|
||||
// Handle the notification with context
|
||||
handler.HandlePushNotification(ctx, handlerCtx, notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// shouldSkipNotification checks if a notification type should be ignored by the push notification
|
||||
// processor and handled by other specialized systems instead (pub/sub, streams, keyspace, etc.).
|
||||
func shouldSkipNotification(notificationType string) bool {
|
||||
switch notificationType {
|
||||
// Pub/Sub notifications - handled by pub/sub system
|
||||
case "message", // Regular pub/sub message
|
||||
"pmessage", // Pattern pub/sub message
|
||||
"subscribe", // Subscription confirmation
|
||||
"unsubscribe", // Unsubscription confirmation
|
||||
"psubscribe", // Pattern subscription confirmation
|
||||
"punsubscribe", // Pattern unsubscription confirmation
|
||||
"smessage", // Sharded pub/sub message (Redis 7.0+)
|
||||
"ssubscribe", // Sharded subscription confirmation
|
||||
"sunsubscribe", // Sharded unsubscription confirmation
|
||||
|
||||
// Stream notifications - handled by stream consumers
|
||||
"xread-from", // Stream reading notifications
|
||||
"xreadgroup-from", // Stream consumer group notifications
|
||||
|
||||
// Client tracking notifications - handled by client tracking system
|
||||
"invalidate", // Client-side caching invalidation
|
||||
|
||||
// Keyspace notifications - handled by keyspace notification subscribers
|
||||
// Note: Keyspace notifications typically have prefixes like "__keyspace@0__:" or "__keyevent@0__:"
|
||||
// but we'll handle the base notification types here
|
||||
"expired", // Key expiration events
|
||||
"evicted", // Key eviction events
|
||||
"set", // Key set events
|
||||
"del", // Key deletion events
|
||||
"rename", // Key rename events
|
||||
"move", // Key move events
|
||||
"copy", // Key copy events
|
||||
"restore", // Key restore events
|
||||
"sort", // Sort operation events
|
||||
"flushdb", // Database flush events
|
||||
"flushall": // All databases flush events
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// VoidProcessor discards all push notifications without processing them.
|
||||
type VoidProcessor struct{}
|
||||
|
||||
// NewVoidProcessor creates a new void push notification processor.
|
||||
func NewVoidProcessor() *VoidProcessor {
|
||||
return &VoidProcessor{}
|
||||
}
|
||||
|
||||
// GetHandler returns nil for void processor since it doesn't maintain handlers.
|
||||
func (v *VoidProcessor) GetHandler(pushNotificationName string) Handler {
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterHandler returns an error for void processor since it doesn't maintain handlers.
|
||||
// This helps developers identify when they're trying to register handlers on disabled push notifications.
|
||||
func (v *VoidProcessor) RegisterHandler(pushNotificationName string, handler Handler, protected bool) error {
|
||||
return fmt.Errorf("cannot register push notification handler '%s': push notifications are disabled (using void processor)", pushNotificationName)
|
||||
}
|
||||
|
||||
// UnregisterHandler returns an error for void processor since it doesn't maintain handlers.
|
||||
// This helps developers identify when they're trying to unregister handlers on disabled push notifications.
|
||||
func (v *VoidProcessor) UnregisterHandler(pushNotificationName string) error {
|
||||
return fmt.Errorf("cannot unregister push notification handler '%s': push notifications are disabled (using void processor)", pushNotificationName)
|
||||
}
|
||||
|
||||
// ProcessPendingNotifications for VoidProcessor does nothing since push notifications
|
||||
// are only available in RESP3 and this processor is used for RESP2 connections.
|
||||
// This avoids unnecessary buffer scanning overhead.
|
||||
func (v *VoidProcessor) ProcessPendingNotifications(ctx context.Context, handlerCtx HandlerContext, rd *proto.Reader) error {
|
||||
// VoidProcessor is used for RESP2 connections where push notifications are not available.
|
||||
// Since push notifications only exist in RESP3, we can safely skip all processing
|
||||
// to avoid unnecessary buffer scanning overhead.
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user