четверг, 16 октября 2025 г.

25.10.14, DeepSeek, Auto, Init, Attribute, Registration

25.10.14, DeepSeek, Auto, Init, Attribute, Registration

Блестящая идея! Это именно тот подход, который устраняет рутину и ошибки. Давайте реализуем автоматическую регистрацию через атрибуты и конфигурацию.

1. Атрибуты для автоматической регистрации

csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace EventHub.AutoRegistration
{
    // Атрибут для пометки обработчиков
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class EventHandlerAttribute : Attribute
    {
        public string EventType { get; }
        public string EventKey { get; }
        public int Priority { get; set; } = 1;

        public EventHandlerAttribute(string eventType, string eventKey = null)
        {
            EventType = eventType;
            EventKey = eventKey ?? eventType;
        }
    }

    // Атрибут для пометки обработчиков с поддержкой нескольких событий
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class HandlesEventsAttribute : Attribute
    {
        public string[] EventTypes { get; }
        public string Group { get; set; } = "default";

        public HandlesEventsAttribute(params string[] eventTypes)
        {
            EventTypes = eventTypes;
        }
    }

    // Расширенный атрибут с дополнительными настройками
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class SmartEventHandlerAttribute : Attribute
    {
        public string EventType { get; }
        public string Condition { get; set; }
        public bool Enabled { get; set; } = true;
        public int Order { get; set; } = 1;
        public string[] DependsOn { get; set; } = Array.Empty<string>();

        public SmartEventHandlerAttribute(string eventType)
        {
            EventType = eventType;
        }
    }
}

2. Автоматический регистратор

csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading.Tasks;

namespace EventHub.AutoRegistration
{
    public interface IAutoEventRegistrar
    {
        Task RegisterFromAssemblyAsync(Assembly assembly);
        Task RegisterFromConfigurationAsync(string jsonConfig);
        Task RegisterFromAttributesAsync();
        List<HandlerRegistration> GetRegistrations();
    }

    public class HandlerRegistration
    {
        public string EventType { get; set; }
        public Type HandlerType { get; set; }
        public int Priority { get; set; }
        public string Condition { get; set; }
        public bool Enabled { get; set; }
        public string[] DependsOn { get; set; }
    }

    public class AutoEventRegistrar : IAutoEventRegistrar
    {
        private readonly IEventHub _eventHub;
        private readonly IServiceProvider _serviceProvider;
        private readonly List<HandlerRegistration> _registrations;

        public AutoEventRegistrar(IEventHub eventHub, IServiceProvider serviceProvider)
        {
            _eventHub = eventHub;
            _serviceProvider = serviceProvider;
            _registrations = new List<HandlerRegistration>();
        }

        public async Task RegisterFromAssemblyAsync(Assembly assembly)
        {
            var handlerTypes = assembly.GetTypes()
                .Where(t => t.GetCustomAttributes<EventHandlerAttribute>().Any() ||
                           t.GetCustomAttributes<HandlesEventsAttribute>().Any() ||
                           t.GetCustomAttributes<SmartEventHandlerAttribute>().Any())
                .ToList();

            foreach (var handlerType in handlerTypes)
            {
                await RegisterHandlerAsync(handlerType);
            }
        }

        public async Task RegisterFromConfigurationAsync(string jsonConfig)
        {
            var config = JsonSerializer.Deserialize<EventHandlersConfig>(jsonConfig);
            
            foreach (var handlerConfig in config.Handlers)
            {
                var handlerType = Type.GetType(handlerConfig.HandlerType);
                if (handlerType != null)
                {
                    await RegisterHandlerWithConfigAsync(handlerType, handlerConfig);
                }
            }
        }

        public async Task RegisterFromAttributesAsync()
        {
            // Регистрирует все обработчики из текущей сборки
            await RegisterFromAssemblyAsync(Assembly.GetExecutingAssembly());
        }

        private async Task RegisterHandlerAsync(Type handlerType)
        {
            // Регистрация через EventHandlerAttribute
            var eventHandlerAttrs = handlerType.GetCustomAttributes<EventHandlerAttribute>();
            foreach (var attr in eventHandlerAttrs)
            {
                await RegisterHandlerForEventAsync(handlerType, attr.EventType, attr.EventKey, attr.Priority);
                _registrations.Add(new HandlerRegistration
                {
                    EventType = attr.EventType,
                    HandlerType = handlerType,
                    Priority = attr.Priority,
                    Enabled = true
                });
            }

            // Регистрация через HandlesEventsAttribute
            var handlesEventsAttrs = handlerType.GetCustomAttributes<HandlesEventsAttribute>();
            foreach (var attr in handlesEventsAttrs)
            {
                foreach (var eventType in attr.EventTypes)
                {
                    await RegisterHandlerForEventAsync(handlerType, eventType, eventType, 1);
                    _registrations.Add(new HandlerRegistration
                    {
                        EventType = eventType,
                        HandlerType = handlerType,
                        Priority = 1,
                        Enabled = true
                    });
                }
            }

            // Регистрация через SmartEventHandlerAttribute
            var smartAttrs = handlerType.GetCustomAttributes<SmartEventHandlerAttribute>();
            foreach (var attr in smartAttrs)
            {
                if (attr.Enabled)
                {
                    await RegisterHandlerForEventAsync(handlerType, attr.EventType, attr.EventType, attr.Order);
                    _registrations.Add(new HandlerRegistration
                    {
                        EventType = attr.EventType,
                        HandlerType = handlerType,
                        Priority = attr.Order,
                        Condition = attr.Condition,
                        Enabled = attr.Enabled,
                        DependsOn = attr.DependsOn
                    });
                }
            }
        }

        private async Task RegisterHandlerWithConfigAsync(Type handlerType, HandlerConfig config)
        {
            if (config.Enabled)
            {
                var handlerInstance = _serviceProvider.GetService(handlerType);
                if (handlerInstance != null)
                {
                    // Используем рефлексию для вызова Subscribe
                    await RegisterViaReflectionAsync(handlerType, handlerInstance, config.EventType);
                    
                    _registrations.Add(new HandlerRegistration
                    {
                        EventType = config.EventType,
                        HandlerType = handlerType,
                        Priority = config.Priority,
                        Condition = config.Condition,
                        Enabled = config.Enabled
                    });
                }
            }
        }

        private async Task RegisterHandlerForEventAsync(Type handlerType, string eventType, string eventKey, int priority)
        {
            var handlerInstance = _serviceProvider.GetService(handlerType);
            if (handlerInstance != null)
            {
                await RegisterViaReflectionAsync(handlerType, handlerInstance, eventType);
            }
        }

        private async Task RegisterViaReflectionAsync(Type handlerType, object handlerInstance, string eventType)
        {
            // Находим интерфейс IMessageHandler<T>
            var handlerInterface = handlerType.GetInterfaces()
                .FirstOrDefault(i => i.IsGenericType && 
                                   i.GetGenericTypeDefinition() == typeof(IMessageHandler<>));

            if (handlerInterface != null)
            {
                var messageType = handlerInterface.GetGenericArguments()[0];
                
                // Создаем generic метод Subscribe
                var subscribeMethod = typeof(IEventHub).GetMethod("Subscribe")?
                    .MakeGenericMethod(messageType);
                
                // Вызываем Subscribe
                subscribeMethod?.Invoke(_eventHub, new[] { handlerInstance });
            }
        }

        public List<HandlerRegistration> GetRegistrations() => _registrations;
    }

    // Модели для JSON конфигурации
    public class EventHandlersConfig
    {
        public List<HandlerConfig> Handlers { get; set; } = new List<HandlerConfig>();
    }

    public class HandlerConfig
    {
        public string EventType { get; set; }
        public string HandlerType { get; set; }
        public int Priority { get; set; } = 1;
        public string Condition { get; set; }
        public bool Enabled { get; set; } = true;
    }
}

3. Примеры обработчиков с атрибутами

csharp
using System;
using System.Threading.Tasks;

namespace EventHub.Examples.AutoRegistration
{
    // Базовые события
    public class UserRegisteredEvent : Message
    {
        public override string Type => "UserRegistered";
        public string Username { get; set; }
        public string Email { get; set; }
    }

    public class OrderCreatedEvent : Message
    {
        public override string Type => "OrderCreated";
        public string OrderId { get; set; }
        public decimal Amount { get; set; }
    }

    public class PaymentProcessedEvent : Message
    {
        public override string Type => "PaymentProcessed";
        public string PaymentId { get; set; }
        public bool Success { get; set; }
    }

    // ОБРАБОТЧИКИ С АТТРИБУТАМИ

    [EventHandler("UserRegistered", "user-registration")]
    [EventHandler("UserRegistered", "user-audit", Priority = 2)]
    public class EmailWelcomeHandler : IMessageHandler<UserRegisteredEvent>
    {
        public async Task HandleAsync(UserRegisteredEvent message)
        {
            Console.WriteLine($"[Email] Sending welcome to {message.Email}");
            await Task.Delay(100);
        }
    }

    [HandlesEvents("UserRegistered", "OrderCreated", "PaymentProcessed")]
    public class AnalyticsHandler : 
        IMessageHandler<UserRegisteredEvent>,
        IMessageHandler<OrderCreatedEvent>,
        IMessageHandler<PaymentProcessedEvent>
    {
        public async Task HandleAsync(UserRegisteredEvent message)
        {
            Console.WriteLine($"[Analytics] Tracking user registration: {message.Username}");
            await Task.Delay(50);
        }

        public async Task HandleAsync(OrderCreatedEvent message)
        {
            Console.WriteLine($"[Analytics] Tracking order: {message.OrderId}");
            await Task.Delay(50);
        }

        public async Task HandleAsync(PaymentProcessedEvent message)
        {
            Console.WriteLine($"[Analytics] Tracking payment: {message.PaymentId}");
            await Task.Delay(50);
        }
    }

    [SmartEventHandler("OrderCreated", Condition = "Amount > 100", Order = 1)]
    [SmartEventHandler("PaymentProcessed", Order = 2, DependsOn = new[] { "OrderCreated" })]
    public class PremiumOrderHandler : 
        IMessageHandler<OrderCreatedEvent>,
        IMessageHandler<PaymentProcessedEvent>
    {
        public async Task HandleAsync(OrderCreatedEvent message)
        {
            if (message.Amount > 100)
            {
                Console.WriteLine($"[Premium] Processing premium order: {message.OrderId}");
                await Task.Delay(200);
            }
        }

        public async Task HandleAsync(PaymentProcessedEvent message)
        {
            Console.WriteLine($"[Premium] Handling premium payment: {message.PaymentId}");
            await Task.Delay(100);
        }
    }

    [EventHandler("OrderCreated", "order-notification")]
    public class NotificationHandler : IMessageHandler<OrderCreatedEvent>
    {
        public async Task HandleAsync(OrderCreatedEvent message)
        {
            Console.WriteLine($"[Notification] Order {message.OrderId} created");
            await Task.Delay(150);
        }
    }
}

4. JSON конфигурация

json
{
  "Handlers": [
    {
      "EventType": "UserRegistered",
      "HandlerType": "EventHub.Examples.AutoRegistration.EmailWelcomeHandler, EventHub",
      "Priority": 1,
      "Enabled": true
    },
    {
      "EventType": "UserRegistered", 
      "HandlerType": "EventHub.Examples.AutoRegistration.AnalyticsHandler, EventHub",
      "Priority": 2,
      "Enabled": true
    },
    {
      "EventType": "OrderCreated",
      "HandlerType": "EventHub.Examples.AutoRegistration.PremiumOrderHandler, EventHub", 
      "Priority": 1,
      "Condition": "Amount > 100",
      "Enabled": true
    },
    {
      "EventType": "OrderCreated",
      "HandlerType": "EventHub.Examples.AutoRegistration.NotificationHandler, EventHub",
      "Priority": 3,
      "Enabled": true
    }
  ]
}

5. Использование автоматической регистрации

csharp
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

namespace EventHub.Examples.AutoRegistration
{
    public class AutoRegistrationExample
    {
        public static async Task Run()
        {
            // Настройка DI
            var services = new ServiceCollection();
            
            // Регистрируем EventHub
            services.AddSingleton<IEventHub, EventHub>();
            services.AddSingleton<IAutoEventRegistrar, AutoEventRegistrar>();
            
            // Регистрируем обработчики
            services.AddTransient<EmailWelcomeHandler>();
            services.AddTransient<AnalyticsHandler>();
            services.AddTransient<PremiumOrderHandler>();
            services.AddTransient<NotificationHandler>();

            var serviceProvider = services.BuildServiceProvider();

            // АВТОМАТИЧЕСКАЯ РЕГИСТРАЦИЯ
            var registrar = serviceProvider.GetRequiredService<IAutoEventRegistrar>();
            
            // Способ 1: Из атрибутов
            await registrar.RegisterFromAttributesAsync();
            
            // Способ 2: Из конфигурации JSON
            var jsonConfig = File.ReadAllText("event-handlers.json");
            await registrar.RegisterFromConfigurationAsync(jsonConfig);
            
            // Способ 3: Из конкретной сборки
            await registrar.RegisterFromAssemblyAsync(typeof(AutoRegistrationExample).Assembly);

            // Проверяем что зарегистрировалось
            var registrations = registrar.GetRegistrations();
            Console.WriteLine($"Registered {registrations.Count} handler-event pairs:");
            
            foreach (var reg in registrations)
            {
                Console.WriteLine($"  {reg.EventType} -> {reg.HandlerType.Name} (Priority: {reg.Priority})");
            }

            // ТЕСТИРУЕМ
            var eventHub = serviceProvider.GetRequiredService<IEventHub>();
            
            await eventHub.PublishAsync(new UserRegisteredEvent 
            { 
                Username = "john_doe", 
                Email = "john@example.com" 
            });

            await eventHub.PublishAsync(new OrderCreatedEvent 
            { 
                OrderId = "ORD-001", 
                Amount = 150.00m 
            });

            await eventHub.PublishAsync(new PaymentProcessedEvent 
            { 
                PaymentId = "PAY-001", 
                Success = true 
            });
        }
    }
}

6. Расширенная версия с условиями и фильтрацией

csharp
using System;
using System.Linq.Expressions;
using System.Threading.Tasks;

namespace EventHub.AdvancedAutoRegistration
{
    public class ConditionalEventRegistrar : AutoEventRegistrar
    {
        private readonly Dictionary<string, Func<IMessage, bool>> _conditions;

        public ConditionalEventRegistrar(IEventHub eventHub, IServiceProvider serviceProvider) 
            : base(eventHub, serviceProvider)
        {
            _conditions = new Dictionary<string, Func<IMessage, bool>>();
        }

        public void AddCondition(string conditionName, Func<IMessage, bool> condition)
        {
            _conditions[conditionName] = condition;
        }

        protected override async Task RegisterHandlerWithConfigAsync(Type handlerType, HandlerConfig config)
        {
            if (config.Enabled && ShouldRegister(config))
            {
                await base.RegisterHandlerWithConfigAsync(handlerType, config);
            }
        }

        private bool ShouldRegister(HandlerConfig config)
        {
            if (string.IsNullOrEmpty(config.Condition))
                return true;

            // Поддержка простых условий
            return config.Condition.ToLower() switch
            {
                "always" => true,
                "never" => false,
                "debug" => System.Diagnostics.Debugger.IsAttached,
                "production" => !System.Diagnostics.Debugger.IsAttached,
                _ when _conditions.ContainsKey(config.Condition) => 
                    _conditions[config.Condition](new DummyMessage()),
                _ => true
            };
        }

        private class DummyMessage : IMessage
        {
            public string Id => "dummy";
            public DateTime Timestamp => DateTime.UtcNow;
            public string Type => "Dummy";
        }
    }

    // ДИНАМИЧЕСКАЯ РЕГИСТРАЦИЯ В RUNTIME
    public class DynamicEventHandlerRegistrar
    {
        private readonly IEventHub _eventHub;
        private readonly IServiceProvider _serviceProvider;

        public DynamicEventHandlerRegistrar(IEventHub eventHub, IServiceProvider serviceProvider)
        {
            _eventHub = eventHub;
            _serviceProvider = serviceProvider;
        }

        public async Task RegisterFromDiscoveryAsync()
        {
            // Автопоиск всех обработчиков в домене приложения
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            
            foreach (var assembly in assemblies)
            {
                try
                {
                    var handlerTypes = assembly.GetTypes()
                        .Where(t => t.GetInterfaces()
                            .Any(i => i.IsGenericType && 
                                     i.GetGenericTypeDefinition() == typeof(IMessageHandler<>)))
                        .ToList();

                    foreach (var handlerType in handlerTypes)
                    {
                        await TryRegisterHandlerAsync(handlerType);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Error scanning assembly {assembly}: {ex.Message}");
                }
            }
        }

        private async Task TryRegisterHandlerAsync(Type handlerType)
        {
            try
            {
                var handlerInstance = _serviceProvider.GetService(handlerType);
                if (handlerInstance != null)
                {
                    var handlerInterface = handlerType.GetInterfaces()
                        .First(i => i.IsGenericType && 
                                   i.GetGenericTypeDefinition() == typeof(IMessageHandler<>));

                    var messageType = handlerInterface.GetGenericArguments()[0];
                    var subscribeMethod = typeof(IEventHub).GetMethod("Subscribe")
                        ?.MakeGenericMethod(messageType);

                    subscribeMethod?.Invoke(_eventHub, new[] { handlerInstance });
                    
                    Console.WriteLine($"Auto-registered: {handlerType.Name} for {messageType.Name}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Failed to register {handlerType.Name}: {ex.Message}");
            }
        }
    }
}

Преимущества этого подхода:

 Исключение ошибок ручной регистрации

csharp
// БЫЛО (легко ошибиться):
eventHub.Subscribe<OrderCreatedEvent>(orderHandler);
eventHub.Subscribe<UserRegisteredEvent>(userHandler);
// ... и еще 20 строк ручной регистрации

// СТАЛО (автоматически):
await registrar.RegisterFromAttributesAsync();
// ВСЁ! Никаких ошибок, всё поднимается автоматически

 Централизованное управление

  • Все обработчики видны в одном месте (атрибуты или JSON)

  • Легко находить дубликаты

  • Просто отключать/включать обработчики

 Динамическая конфигурация

json
{
  "Handlers": [
    {
      "EventType": "OrderCreated",
      "HandlerType": "MyApp.Handlers.OrderHandler",
      "Enabled": false,  // Временно отключаем
      "Condition": "Environment == 'Production'"
    }
  ]
}

 Без рефакторинга

Добавляем новый обработчик:

csharp
[EventHandler("NewEventType")]
public class NewEventHandler : IMessageHandler<NewEvent>
{
    // Регистрация произойдет автоматически!
}

Итог: Ваша идея блестящая! Это убирает целый класс ошибок и делает код значительно чище и поддерживаемее. 🎯

Комментариев нет:

Отправить комментарий