Implementing a domain event handler template in C # with a simple injector

I am trying to implement a domain event pattern in C # using a Simple Injector .

I simplified my code to be in a single file that can be run as a console application, and excluded the Simple Injector code so that everything was clear for the purpose of this question.

The problem I am facing is that each event can have multiple event handlers and multiple events, but I want to limit my dispatcher to handling events that implement the IEvent interface, so I put this restriction on my Send Method.

This caused problems with how to get an instance from Simple Injector, since every time a Dispatch method called TEvent is of type IEvent (as expected), but I need to get the type of event passed, so that I can get the appropriate handlers from Simple Injector .

Hope my code will explain this a little better:

 interface IEvent { } interface IEventHandler<T> where T : IEvent { void Handle(T @event); } class StandardEvent : IEvent { } class AnotherEvent : IEvent { } class StandardEventHandler : IEventHandler<StandardEvent> { public void Handle(StandardEvent @event) { Console.WriteLine("StandardEvent handled"); } } class AnotherEventHandler : IEventHandler<AnotherEvent> { public void Handle(AnotherEvent @event) { Console.WriteLine("AnotherEvent handled"); } } 

Here is my dispatcher:

 static class Dispatcher { // I need to get the type of @event here so I can get the registered instance from the // IoC container (SimpleInjector), however TEvent is of type IEvent (as expected). // What I need to do here is Get the registered instance from Simple Injector for each // Event Type ie Container.GetAllInstances<IEventHandler<StandardEvent>>() // and Container.GetAllInstances<IEventHandler<AnotherEvent>>() public static void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent { } } class PlainOldObject { public ICollection<IEvent> Events = new List<IEvent> { new StandardEvent(), new AnotherEvent() }; } class StandAlone { static void Main(string[] args) { var poco = new PlainOldObject(); foreach (var @event in poco.Events) { Dispatcher.Dispatch(@event); } } } 

I commented in the dispatch method that my problem. Does anyone know how I should solve this?

Regards, Gary

+4
source share
2 answers

The solution you need depends a bit on how the consumer of the Dispatcher call events. If the consumer always knows the exact type of event at compile time, you can use the generic Dispatch<TEvent>(TEvent) method, as shown above. In this case, the implementation of Dispatcher will be really simple.

If, on the other hand, consumers may not always know the exact type, but just work with the IEvent interface, the generic type argument in Dispatch<TEvent>(TEvent) becomes the useles symbol, and you better define the Dispatch(IEvent) method. This makes the implementation more complicated because you will need to use reflection to solve this problem.

Also note that it would be nice to introduce an IEventDispatcher abstraction. Do not call a static class from your code. Even Udi Dahan (who originally described such a static class a long time ago) now considers this anti-pattern. Instead, add an IEventDispatcher abstraction to classes that require event dispatching.

If all users work with event types that are known at compile time, your implementation will look like this:

 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, the types of events 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); } } } 

Note that using a dynamic keyword has several non-obvious advantages over using the .NET API. For example, when calling a Handle handler method that uses a dynamic one, any exception thrown from the handle will automatically throw. When using MethodInfo.Invoke , on the other hand, the exception will be wrapped in a new exception. This complicates the catch and debugging.

Event handlers can be registered as follows:

 container.RegisterCollection(typeof(IEventHandler<>), listOfAssembliesToSearch); 
+9
source

To use SimpleInjector and dynamically inject a domain event, you can do the following:

In SI registers

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

Then create an 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 enter the correct one, when you create your IDomainEventDistpatcher with a simple injector, you use the factory injector. This is the key to getting all types and the ability to dynamically search them. By doing so, we introduce Func in the DomainEventDispatcher.

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

Now in 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; } } } 

Now it accepts IDomainEvent and creates the correct type and searches for it based on the provided Func.

This is better because now we are not making the class dependency aware of the DI implementation we are using. Very similar to Steven anwser above (with small tweaks), just a thought will also provide a complete example.

0
source

Source: https://habr.com/ru/post/1240457/


All Articles