Skip to content

Improvments #70

Description

@kurmann

Deine Implementierung ist bereits gut strukturiert und berücksichtigt viele Aspekte wie Thread-Sicherheit und Flexibilität. Dennoch gibt es einige Verbesserungspotenziale, die die Einfachheit, Flexibilität und Performance weiter verbessern könnten:

Verbesserungspotenziale

  1. ConcurrentDictionary-Erstellung und Handler-Listen-Initialisierung

    • Das Hinzufügen oder Aktualisieren von Handlern könnte optimiert werden, indem die ConcurrentDictionary-Methoden effizienter genutzt werden. Derzeit wird beim AddOrUpdate-Aufruf immer eine neue Liste erstellt, selbst wenn nur ein neuer Handler hinzugefügt wird. Dies könnte verbessert werden.
    _handlers.AddOrUpdate(messageType,
        new List<Func<IEventMessage, Task>> { genericHandler },
        (_, existingHandlers) =>
        {
            lock (existingHandlers)
            {
                existingHandlers.Add(genericHandler);
                _logger.LogInformation("Subscriber {handlerMethodName} added for message type {messageTypeName}.", handler.Method.Name, messageType.Name);
                return existingHandlers;
            }
        }
    );
  2. Asynchrone Verarbeitung

    • Die Performance bei der Verarbeitung von Nachrichten könnte durch parallele Ausführung der Handler verbessert werden. Anstatt die Handler nacheinander auszuführen, könntest du sie parallel ausführen und auf die Ergebnisse warten.
    public async Task Publish<TMessage>(TMessage message) where TMessage : IEventMessage
    {
        Type messageType = typeof(TMessage);
        if (_handlers.TryGetValue(messageType, out var subscribers))
        {
            var tasks = subscribers.Select(handler => handler(message)).ToList();
            await Task.WhenAll(tasks).ConfigureAwait(false);
        }
        else
        {
            _logger.LogWarning("No subscribers found for message type {messageType}.", messageType.Name);
        }
    }
  3. Weak References

    • Um das Risiko von Speicherlecks weiter zu minimieren, könntest du WeakReference für die Handler verwenden. Dadurch stellst du sicher, dass die Handler nicht verhindern, dass die Objekte, auf die sie verweisen, vom Garbage Collector gesammelt werden.
    private readonly ConcurrentDictionary<Type, List<WeakReference<Func<IEventMessage, Task>>>> _handlers = new();
    
    public void Subscribe<TMessage>(Func<TMessage, Task> handler) where TMessage : IEventMessage
    {
        Type messageType = typeof(TMessage);
        Task genericHandler(IEventMessage message) => handler((TMessage)message);
        var weakHandler = new WeakReference<Func<IEventMessage, Task>>(genericHandler);
    
        _handlers.AddOrUpdate(messageType,
            new List<WeakReference<Func<IEventMessage, Task>>> { weakHandler },
            (_, existingHandlers) =>
            {
                lock (existingHandlers)
                {
                    existingHandlers.Add(weakHandler);
                    _logger.LogInformation("Subscriber {handlerMethodName} added for message type {messageTypeName}.", handler.Method.Name, messageType.Name);
                    return existingHandlers;
                }
            }
        );
    }
    
    public async Task Publish<TMessage>(TMessage message) where TMessage : IEventMessage
    {
        Type messageType = typeof(TMessage);
        if (_handlers.TryGetValue(messageType, out var subscribers))
        {
            var tasks = new List<Task>();
            foreach (var weakHandler in subscribers)
            {
                if (weakHandler.TryGetTarget(out var handler))
                {
                    tasks.Add(handler(message));
                }
            }
            await Task.WhenAll(tasks).ConfigureAwait(false);
        }
        else
        {
            _logger.LogWarning("No subscribers found for message type {messageType}.", messageType.Name);
        }
    }
  4. Abstraktion und Erweiterbarkeit

    • Um die Flexibilität und Erweiterbarkeit zu verbessern, könntest du eine Abstraktionsschicht für die Speicherung und Verwaltung der Handler einführen. Dies erleichtert zukünftige Änderungen und Erweiterungen, z.B. das Hinzufügen von Prioritäten für Handler.
    public interface IHandlerStorage
    {
        void AddHandler<TMessage>(Func<IEventMessage, Task> handler) where TMessage : IEventMessage;
        void RemoveHandler<TMessage>(Func<IEventMessage, Task> handler) where TMessage : IEventMessage;
        List<Func<IEventMessage, Task>> GetHandlers<TMessage>() where TMessage : IEventMessage;
    }
    
    public class InMemoryHandlerStorage : IHandlerStorage
    {
        private readonly ConcurrentDictionary<Type, List<Func<IEventMessage, Task>>> _handlers = new();
    
        public void AddHandler<TMessage>(Func<IEventMessage, Task> handler) where TMessage : IEventMessage
        {
            Type messageType = typeof(TMessage);
            _handlers.AddOrUpdate(messageType,
                new List<Func<IEventMessage, Task>> { handler },
                (_, existingHandlers) =>
                {
                    lock (existingHandlers)
                    {
                        existingHandlers.Add(handler);
                        return existingHandlers;
                    }
                }
            );
        }
    
        public void RemoveHandler<TMessage>(Func<IEventMessage, Task> handler) where TMessage : IEventMessage
        {
            Type messageType = typeof(TMessage);
            if (_handlers.TryGetValue(messageType, out var subscribers))
            {
                lock (subscribers)
                {
                    subscribers.RemoveAll(h => h.Equals(handler));
                    if (subscribers.Count == 0)
                    {
                        _handlers.TryRemove(messageType, out _);
                    }
                }
            }
        }
    
        public List<Func<IEventMessage, Task>> GetHandlers<TMessage>() where TMessage : IEventMessage
        {
            Type messageType = typeof(TMessage);
            _handlers.TryGetValue(messageType, out var subscribers);
            return subscribers;
        }
    }
    
    public class MessageService : IMessageService
    {
        private readonly IHandlerStorage _handlerStorage;
        private readonly ILogger<MessageService> _logger;
    
        public MessageService(IHandlerStorage handlerStorage, ILogger<MessageService> logger)
        {
            _handlerStorage = handlerStorage ?? throw new ArgumentNullException(nameof(handlerStorage));
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }
    
        public async Task Publish<TMessage>(TMessage message) where TMessage : IEventMessage
        {
            var handlers = _handlerStorage.GetHandlers<TMessage>();
            if (handlers != null)
            {
                var tasks = handlers.Select(handler => handler(message)).ToList();
                await Task.WhenAll(tasks).ConfigureAwait(false);
            }
            else
            {
                _logger.LogWarning("No subscribers found for message type {messageType}.", typeof(TMessage).Name);
            }
        }
    
        public void Subscribe<TMessage>(Func<TMessage, Task> handler) where TMessage : IEventMessage
        {
            Task genericHandler(IEventMessage message) => handler((TMessage)message);
            _handlerStorage.AddHandler<TMessage>(genericHandler);
            _logger.LogInformation("Subscriber {handlerMethodName} added for message type {messageTypeName}.", handler.Method.Name, typeof(TMessage).Name);
        }
    
        public void Unsubscribe<TMessage>(Func<TMessage, Task> handler) where TMessage : IEventMessage
        {
            Task genericHandler(IEventMessage message) => handler((TMessage)message);
            _handlerStorage.RemoveHandler<TMessage>(genericHandler);
            _logger.LogInformation("Subscriber {handlerMethodName} removed for message type {messageTypeName}.", handler.Method.Name, typeof(TMessage).Name);
        }
    }

Fazit

Mit diesen Verbesserungen könntest du die Einfachheit, Flexibilität und Performance deines Nachrichtendienstes weiter erhöhen. Besonders die Verwendung von WeakReference könnte die Verwaltung von Handlers verbessern, und eine abstrakte Speicherschicht kann die Erweiterbarkeit der Lösung erhöhen.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions