Implementing Domain Event Handler pattern in C# with Simple Injector

后端 未结 3 879
南方客
南方客 2021-02-02 04:20

I am trying to implement the Domain Event pattern in C# using Simple Injector.

I have simplified my code to be in one file that can be ran as a console app and have excl

相关标签:
3条回答
  • 2021-02-02 04:52

    Here is a faster version using cached delegates. No dynamic and no reflection after the first call. Uses IServiceProvider from the default Microsoft DI but that can be easily changed. Expression trees can be used too, but they take more memory:

    public class EventDispatcherService : IEventDispatcher
    {
        private static readonly ConcurrentDictionary<Type, IEnumerable<Func<object, Task>>> HandlersCache
            = new ConcurrentDictionary<Type, IEnumerable<Func<object, Task>>>();
    
        private static readonly Type HandlerType = typeof(IEventHandler<>);
    
        private static readonly MethodInfo MakeDelegateMethod = typeof(EventDispatcherService)
            .GetMethod(nameof(MakeDelegate), BindingFlags.Static | BindingFlags.NonPublic);
    
        private static readonly Type OpenGenericFuncType = typeof(Func<,>);
    
        private static readonly Type TaskType = typeof(Task);
    
        private readonly IServiceProvider serviceProvider;
    
        public EventDispatcherService(IServiceProvider serviceProvider)
            => this.serviceProvider = serviceProvider;
    
        public async Task Dispatch(IDomainEvent domainEvent)
        {
            var eventHandlers = HandlersCache.GetOrAdd(domainEvent.GetType(), eventType =>
            {
                var eventHandlerType = HandlerType.MakeGenericType(eventType);
    
                var makeDelegate = MakeDelegateMethod.MakeGenericMethod(eventType);
    
                var funcType = OpenGenericFuncType.MakeGenericType(eventType, TaskType);
    
                return this.serviceProvider
                    .GetServices(eventHandlerType)
                    .Select(handler => handler
                        .GetType()
                        .GetMethod("Handle")
                        .CreateDelegate(funcType, handler))
                    .Select(handlerDelegateConcrete => (Func<object, Task>)makeDelegate
                        .Invoke(null, new object[] { handlerDelegateConcrete }))
                    .ToList();
            });
    
            foreach (var eventHandler in eventHandlers)
            {
                await eventHandler(domainEvent);
            }
        }
    
        private static Func<object, Task> MakeDelegate<T>(Func<T, Task> action)
            => value => action((T)value);
    }
    

    And this is the event handler interface:

    public interface IEventHandler<in TEvent>
        where TEvent : IDomainEvent
    {
        Task Handle(TEvent domainEvent);
    }
    
    0 讨论(0)
  • 2021-02-02 05:09

    The solution you need is a bit dependent on how the consumer of the Dispatcher calls events. If the consumer always knows the exact type of the event at compile time, you can use the generic Dispatch<TEvent>(TEvent) method as you show above. In that case the Dispatcher's implementation will be really straightforward.

    If on the other hand, consumers might not always know the exact type, but simply work with the IEvent interface, the generic type argument in Dispatch<TEvent>(TEvent) becomes useless, and you would be better off defining a Dispatch(IEvent) method. This makes the implementation a bit more sophisticated, because you will need to use reflection to solve this.

    Also note that it would be good to introduce an IEventDispatcher abstraction. Don't call a static class from within your code. Even Udi Dahan (who initially described such static class a long time ago) now considers this an anti-pattern. Instead, inject an IEventDispatcher abstraction into classes that require event dispatching.

    In case all consumers work with event types that are known at compile time, your implementation will look as follows:

    public interface IEventDispatcher
    {
        void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent;
    }
    
    private sealed class Dispatcher : IEventDispatcher
    {
        private readonly Container container;
        public Dispatcher(Container container) {
            this.container = container;
        }
    
        public void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent {
            if (@event == null) throw new ArgumentNullException("event");
    
            var handlers = this.container.GetAllInstances<IEventHandler<TEvent>>();
    
            foreach (var handler in handlers) {
                handler.Handle(@event);
            }
        }
    }
    

    If on the other hand, event types are unknown, you can use the following code:

    public interface IEventDispatcher
    {
        void Dispatch(IEvent @event);
    }
    
    private sealed class Dispatcher : IEventDispatcher
    {
        private readonly Container container;
        public Dispatcher(Container container) {
            this.container = container;
        }
    
        public void Dispatch(IEvent @event) {
            if (@event == null) throw new ArgumentNullException("event");
    
            Type handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType());
    
            var handlers = this.container.GetAllInstances(handlerType);
    
            foreach (dynamic handler in handlers) {
                handler.Handle((dynamic)@event);
            }
        }
    }
    

    Do note that the use of the dynamic keyword has a few unobvious advantages over using the .NET reflection API. For instance, when calling the handler's Handle method using dynamic, any exception thrown from the handle will bubble up directly. When using MethodInfo.Invoke on the other hand, the exception will be wrapped in a new exception. This makes catching and debugging harder.

    Your event handlers can be registered as follows:

    container.RegisterCollection(typeof(IEventHandler<>), listOfAssembliesToSearch);
    
    0 讨论(0)
  • 2021-02-02 05:19

    To use SimpleInjector and get the domain event injected dynamically you could do the following:

    In the SI registrations

    _container.Register(typeof(IDomainEventHandler<>), new[] { typeof(IDomainEventHandler<>).Assembly});
    

    Then create a event

     public class PolicyAddressChangedEvent : IDomainEvent
        {
            public Address NewAddress { get;  }
            public Address OriginalAddress { get;  }
    
            public PolicyAddressChangedEvent(Address oldBillingAddress, Address newbillingAddress)
            {
                OriginalAddress = oldBillingAddress;
                NewAddress = newbillingAddress;
            }
        }
    

    Then create a handler for the event

    public class PolicyAddressChangeHandler : IDomainEventHandler<PolicyAddressChangedEvent>
        {
            private readonly ILoggingService _loggingService;
    
            public PolicyAddressChangeHandler(ILoggingService loggingService)
            {
                _loggingService = loggingService;
            }
    
            public void Handle(PolicyAddressChangedEvent domainEvent)
            {
                _loggingService.Info("New policy address recorded", new Dictionary<string, object> { { "new address", domainEvent.NewAddress } }, "FrameworkSample");
                //this could be event hub, queues, or signalR messages, updating a data warehouse, sending emails, or even updating other domain contexts
            }
        }
    

    Now to inject the correct one when you create your IDomainEventDistpatcher with simple injector you use a factory injector. This is the key to getting all the types and being able to look them up dynamically. By doing it like this we are injecting a Func into the DomainEventDispatcher.

     _container.RegisterSingleton<IDomainEventDispatcher>(() =>
                                                            {
                                                                return new DomainEventDispatcher(type => _container.GetInstance(type));
                                                            });
    

    Now in the DomainEventDispatcher we have

    public class DomainEventDispatcher : IDomainEventDispatcher
        {
            private readonly Func<Type, object> _handlerLookup;
    
            public DomainEventDispatcher(Func<Type, object> handlerLookup)
            {
                _handlerLookup = handlerLookup;
            }
    
            public void Dispatch(IDomainEvent domainEvent)
            {
                Type handlerType = typeof(IDomainEventHandler<>).MakeGenericType(domainEvent.GetType());
                var handler = GetHandler(handlerType);
                if (handler != null)
                {
                    handler.Handle((dynamic)domainEvent);
                }
            }
    
            private dynamic GetHandler(Type filterType)
            {
                try
                {
                    object handler = _handlerLookup.Invoke(filterType);
                    return handler;
                }
                catch (Exception)
                {
                    return null;
                }
            }
        }
    

    This now takes the IDomainEvent and creates the correct type and looks that up based on the Func provided.

    This is better because now we dont force the dependency on the class to know about the DI implementation we are using. Very similar to Steven's anwser above (with some small tweeks), just thought would also provide a complete example too.

    0 讨论(0)
提交回复
热议问题