I put a question here: Raising Domain Events For Multiple Subscribers and the answer led me to the following pattern where I can have an IEventPublisher like so:
public interface IEventPublisher<T>
{
void Publish(T data);
}
and an IEventSubscriber like so:
public interface IEventSubscriber<T>
{
void Handle(T data);
}
The problem with this is that I need to pass an instance of each publisher to a constructor like so:
public Service(IEventPublisher<ThingyChangedEvent> publisherThingyChanged)
{
// Set publisher to local variable
}
// then call this in a method
_publisherThingyChanged.Publish(new ThingyChangedEvent { ThingyId = model.Id});
What I would ideally like to be able to do is have a generic publisher which contains any IEventPublishers so I can call somthing like:
_genericPublisher.Publish(new ThingyChangedEvent { ThingyId = model.Id});
I can't figure out how to do this though as I can't pass a collection of IEventPublisher without defining T as in this example as ThingyChangedEvent whereas what I want is to determine the publisher based on the type that is passed to the generic publisher.
Any suggestions much appreciated.
EDIT:
OK using the answer below and some info from here: http://www.udidahan.com/2009/06/14/domain-events-salvation/ I came up with the following but it's not quite there:
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);
}
}
}
I can now take an instance of IEventManager eventManager in a constructor resolved by autofac and call it as follows:
_eventManager.Publish<ThingyChangedEvent>(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
Here is what I don't like about this solution:
I don't want to take an instance of ILifetimeScope in the constructor, I want to be able to take a collection of IEventSubscribersProvider but autofac won't resolve this if I ask for say:
IEnumerable<IEventSubscribersProvider<IEvent>>
I can only resolve it if I pass the type to the Publish and call:
Resolve<IEventSubscribersProvider<T>>.
The second issue is not a huge deal but would be nice to be able to call publish without having to pass the type as well like so:
_eventManager.Publish(new ThingyChangedEvent() { ThingyId = Guid.NewGuid() });
I think if anyone has any suggestions of how to solve these two issues, particularly issue 1 as I don't like putting a dependency on Autofac in different projects. The only thing I can come up with is a manager class of some kind which explicitly takes what I need as follows:
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?
}
Many thanks for any suggestions.
EDIT 2
After a lot of research and looking at this Autofac Generic Service resolution at runtime I am not sure I can achieve what I want to. The best solution I can come up with is this:
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 have the EventManager take IEventSubscribersProviderFactory in the constructor to remove the dependency on Autofac from that project.
I'll go with this for now but hopefully will find a better solution int the long term.