Domain Event Templates Single Point Queue Events

I posed the question here: Raise domain events for multiple subscribers , and the answer led me to the following template where I can have IEventPublisher:

public interface IEventPublisher<T> { void Publish(T data); } 

and IEventSubscriber:

 public interface IEventSubscriber<T> { void Handle(T data); } 

The problem is that I need to pass an instance of each publisher to the constructor, for example:

 public Service(IEventPublisher<ThingyChangedEvent> publisherThingyChanged) { // Set publisher to local variable } // then call this in a method _publisherThingyChanged.Publish(new ThingyChangedEvent { ThingyId = model.Id}); 

Ideally, I would like to have a universal publisher that contains any IEventPublishers, so I can call somthing like:

 _genericPublisher.Publish(new ThingyChangedEvent { ThingyId = model.Id}); 

I cannot figure out how to do this, although I cannot pass the IEventPublisher collection without defining T, as in this example, as ThingyChangedEvent, whereas I want to determine the publisher based on the type that the common publisher is passed to.

Any suggestions that are very much appreciated.

EDIT:

OK, using the answer below and some information from here: http://www.udidahan.com/2009/06/14/domain-events-salvation/ I came up with the following, but this is not entirely true:

 public interface IEventManager { void Publish<T>(T args) where T : IEvent; } 

public class EventManager: IEventManager {Autofac.ILifetimeScope _container;

 public EventManager(Autofac.ILifetimeScope container) { _container = container; } //Registers a callback for the given domain event public void Publish<T>(T args) where T : IEvent { var subscribersProvider = _container.Resolve<IEventSubscribersProvider<T>>(); foreach (var item in subscribersProvider.GetSubscribersForEvent()) { item.Handle(args); } } 

}

Now I can take an instance of IEventManager eventManager in the constructor allowed by autofac and call it like this:

 _eventManager.Publish<ThingyChangedEvent>(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() }); 

Here's what I don't like about this solution:

I do not want to take an instance of ILifetimeScope in the constructor, I want to be able to take the IEventSubscribersProvider collection, but autofac will not solve this if I ask to say:

  IEnumerable<IEventSubscribersProvider<IEvent>> 

I can only enable it if I pass in the publication type and call:

 Resolve<IEventSubscribersProvider<T>>. 

The second problem is not a huge deal, but it would be nice to be able to invoke the publication without passing it in the same way:

 _eventManager.Publish(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() }); 

I think if anyone has any suggestions for solving these two problems, in particular, question 1, because I donโ€™t like to add Autofac dependency to different projects. The only thing I can come up with is a manager class that explicitly accepts what I need:

 public SomeConstructor( IEventSubscribersProvider<ThingyChangedEvent> thingChangedSubscribeProviders, IEventSubscribersProvider<SomeOtherEvent> someOtherSubscribeProviders, etc....) { // Maybe take the EventManager as well and add them to it somehow but would be // far easier to take a collection of these objects somehow? } 

Thanks so much for any suggestions.

EDIT 2

After doing a lot of research and looking at this Autofac Generic Service solution at runtime, I'm not sure I can achieve what I want. The best solution I can come up with is the following:

 public interface IEventSubscribersProviderFactory : Amico.IDependency { IEventSubscribersProvider<T> Resolve<T>() where T : IEvent; } public class EventSubscribersProviderFactory : IEventSubscribersProviderFactory { Autofac.ILifetimeScope _container; public EventSubscribersProviderFactory(Autofac.ILifetimeScope container) { _container = container; } public IEventSubscribersProvider<T> Resolve<T>() where T : IEvent { return _container.Resolve<IEventSubscribersProvider<T>>(); } } 

And then the EventManager will take the IEventSubscribersProviderFactory in the constructor to remove the Autofac dependency from this project.

I will go with it for now, but I hope it finds a better solution in the long run.

+4
source share
1 answer

This can be a bit complicated when you have to deal with several types of events. As you probably noticed, you cannot just use the derived generic type and expect to use it as the base generic. NET variance does not support where you want to use it.

You need a โ€œbaseโ€ type that will be the smallest (or narrowest) type that you accept as an โ€œeventโ€. I usually use a token interface like public interface IEvent{} . Of course you could get from Object ; but I find it useful to have marker interfaces so that the fact that you signed or published an โ€œeventโ€ is explicit (and means that you cannot just publish any object, just โ€œeventsโ€).

From the appeal to the aspect of the multiple type, you need to write a class for narrowing (getting the derived type and "publishing" the same object that was transferred to the derived type). Even so, you need to direct your events through the corresponding instance of a narrower one (in order to "circumvent" the variance limitations). Then, of course, you can have several subscribers to the same type of event; so you need something to direct the event to multiple subscribers. This is usually called a multiplexer, which would be a specialization of IEventPublisher . You will need one multiplexer for each type of event, which will use a narrower one. Which multiplexer is used for this event depends on the type, so the collection of multiplexers will be controlled using a dictionary so that you can search for them by type. Then, to publish the event to several subscribers, by type, you simply look at the multiplexer and call its IEventPublisher.Publish method. And what controls the multiplexers is an IEventPublisher type and is usually called Dispatcher (some may call it a router).

For instance:

 public class NarrowingSubscriber<TBase, TDerived> : IEventSubscriber<TBase> where TDerived : TBase where TBase : IEvent { private IEventSubscriber<TDerived> inner; public NarrowingSubscriber(IEventSubscriber<TDerived> inner) { if (inner == null) throw new ArgumentNullException("inner"); this.inner = inner; } public void AttachSubscriber(IEventSubscriber<TDerived> subscriber) { inner = subscriber; } public void Handle(TBase data) { inner.Handle((TDerived)data); } } public class Multiplexor<T> : IEventSubscriber<T> where T : IEvent { private readonly List<IEventSubscriber<T>> subscribers = new List<IEventSubscriber<T>>(); public void AttachSubscriber(IEventSubscriber<T> subscriber) { subscribers.Add(subscriber); } public void RemoveSubscriber(IEventSubscriber<T> subscriber) { subscribers.Remove(subscriber); } public void Handle(T data) { subscribers.ForEach(x => x.Handle(data)); } } public class Dispatcher<TBase> : IEventPublisher<TBase> where TBase : IEvent { private readonly Dictionary<Type, Multiplexor<TBase>> subscriptions = new Dictionary<Type, Multiplexor<TBase>>(); public void Publish(TBase data) { Multiplexor<TBase> multiplexor; if (subscriptions.TryGetValue(data.GetType(), out multiplexor)) { multiplexor.Handle(data); } } public void Subscribe<TEvent>(IEventSubscriber<TEvent> handler) where TEvent : TBase { Multiplexor<TBase> multiplexor; if (!subscriptions.TryGetValue(typeof(TEvent), out multiplexor)) { multiplexor = new Multiplexor<TBase>(); subscriptions.Add(typeof(TEvent), multiplexor); } multiplexor.AttachSubscriber(new NarrowingSubscriber<TBase, TEvent>(handler)); } } 

So you basically post 4 times. Once a dispatcher, once per multiplexer, once to a narrower one and once to a non-infrastructure subscriber. If you had two subscribers ( EventOneEventSubscriber and EventTwoEventSubscriber ) who subscribed to two types of events ( EventOne and EventTwo ), you can create a dispatcher and publish the following events:

 var d = new Dispatcher<IEvent>(); var eventTwoSubscriber = new EventTwoEventSubscriber(); d.Subscribe(eventTwoSubscriber); var eventOneSubscriber = new EventOneEventSubscriber(); d.Subscribe(eventOneSubscriber); d.Publish(new EventOne()); d.Publish(new EventTwo()); 

Of course, events will come from IEvent:

 public class EventOne : IEvent { } public class EventTwo : IEvent { } 

This particular restriction does not allow for multiple event submissions. For example, I could connect to an EventOne subscriber and one subscriber for IEvent . This implementation simply publishes to the EventOne subscriber, if the EventOne published through the dispatcher, it will not publish to the IEvent subscriber. I will leave this as an exercise for the reader :)

Update:

If you hope to do this, try automatically connecting subscribers without creating them (I donโ€™t see much value in this, consider Root Composition ), you can add a fairly simple method to Dispatcher (or elsewhere if necessary) to subscribe to all compatible subscribers:

 public void InitializeSubscribers() { foreach (object subscriber in from assembly in AppDomain.CurrentDomain.GetAssemblies() from type in assembly.GetTypes() where !type.IsAbstract && type.IsClass && !type.ContainsGenericParameters && type.GetInterfaces().Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof (IEventSubscriber<>)) select type.GetConstructor(new Type[0]) into constructorInfo where constructorInfo != null select constructorInfo.Invoke(new object[0])) { Subscribe((dynamic) subscriber); } } 

in which you will use the following:

 var d = new Dispatcher<IEvent>(); d.InitializeSubscribers(); 

... using the container to resolve the Dispatcher<T> object if you want.

+5
source