I have recently completed a course about Microservices and RabbitMQ using ASP.NET Core and noticed that event processing uses dynamic invocation. The code is the following (only relevant parts retained ):
public sealed class RabbitMqBus : IEventBus
{
private readonly IMediator _mediator;
private readonly Dictionary<string, List<Type>> _handlers;
private readonly List<Type> _eventTypes;
}
public void Subscribe<TEvent, THandler>() where TEvent : Event where THandler : IEventHandler<TEvent>
{
string eventName = typeof(TEvent).Name;
var handlerType = typeof(THandler);
if (!_eventTypes.Contains(typeof(TEvent)))
_eventTypes.Add(typeof(TEvent));
if (!_handlers.ContainsKey(eventName))
_handlers.Add(eventName, new List<Type>());
if (_handlers[eventName].Any(s => s == handlerType))
throw new ArgumentException($"Handler type {handlerType.Name} is already registered for {eventName}");
_handlers[eventName].Add(handlerType);
StartBasicConsume<TEvent>();
}
private async Task ConsumerReceived(object sender, BasicDeliverEventArgs e)
{
string eventName = e.RoutingKey;
string message = Encoding.UTF8.GetString(e.Body);
try
{
await ProcessEvent(eventName, message);
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
}
private async Task ProcessEvent(string eventName, string message)
{
if (_handlers.ContainsKey(eventName))
{
using var scope = _serviceScopeFactory.CreateScope();
var subscriptions = _handlers[eventName];
foreach (var subscription in subscriptions)
{
var handler = scope.ServiceProvider.GetService(subscription);
if (handler == null)
continue;
//TODO: check if this can be made typed and avoid messy dynamic invocation at the end
Type eventType = _eventTypes.SingleOrDefault(t => t.Name == eventName);
object @event = JsonConvert.DeserializeObject(message, eventType);
Type concreteType = typeof(IEventHandler<>).MakeGenericType(eventType);
await (Task) concreteType.GetMethod("Handle").Invoke(handler, new[] {@event});
}
}
}
public interface IEventHandler
{
}
public interface IEventHandler<in TEvent>: IEventHandler
where TEvent: Event
{
Task Handle(TEvent @event);
}
My issue is with the code following the TODO because it relies on reflection and dynamic invocation of a method with a clear name ("Handle").
My first improvement is eliminating the magic string through nameof
:
Type eventType = _eventTypes.SingleOrDefault(t => t.Name == eventName);
Event @event = (Event) JsonConvert.DeserializeObject(message, eventType);
Type concreteType = typeof(IEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod(nameof(IEventHandler<Event>.Handle)).Invoke(handler, new object[] { @event });
However, reflection is still used. I understand that this is required because Handle is defined as a generic method (part of a generic interface) and the received event is dynamically constructed.
Is there a way to refactor this code to avoid reflection?
Aucun commentaire:
Enregistrer un commentaire