Search code examples
c#genericsdomain-events

Domain Events pattern single point to queue events


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.


Solution

  • It can get a bit complex when you have to deal with multiple types of events. As you've probably noticed you can't just use a derived generic type and expect to use it like a base generic--.NET variance doesn't support that where you'd want to use it.

    You need a "base" type that would be the minimum (or most narrow) type that you'd accept as an "event". I generally use a marker interface like public interface IEvent{}. You could, of course, derive from Object; but I find it useful to have marker interfaces to make the fact that you're subscribing or publishing an "event" explicit (and means you can't just publish any type of object, just "events").

    From the handling of multiple type aspect, you need to write a class to do the narrowing (taking a derived type and "publish" the same object cast to the derived type). Even with that you need to route your events through an appropriate instance of a narrower (to "get around" the variance limitations). Then, of course, you could have multiple subscribers to the same event type; so you need something to route an event to the multiple subscribers. This is generally called a Multiplexor, which would be a specialization of IEventPublisher. You'll need one multiplexor per event type--which would use the narrower. Which multiplexor to use for a given event depends on type, so that collection of multiplexors would be managed by a dictionary so you can look them up by type. Then, to publish an event to multiple subscribers, by type, you simple look up the multiplexor and call its IEventPublisher.Publish method. And something that manages the multiplexors is a type of IEventPublisher and is generally called a Dispatcher (some might call it a router).

    For example:

    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're basically publishing 4 times. Once to the dispatcher, once to the multiplexor, once to the narrower, and once to the non-infrastructure subscriber. If you had two subscribers (EventOneEventSubscriber and EventTwoEventSubscriber) that subscribed to two types of events (EventOne and EventTwo) then you might create a dispatcher and publish events like this:

    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, the events would derive from IEvent:

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

    This particular limitation does not take into account dispatching events multiple times. For example, I could have on subscriber to EventOne and one subscriber for IEvent. This implementation just publishes to the EventOne subscriber if an EventOne object is published through the dispatcher--it wouldn't publish to the IEvent subscriber. I'll leave that as an exercise for the reader :)

    Update:

    If what you're hoping to do is to auto-wire the subscribers without having to construct them (I don't see much value in this, consider a Composition Root) then you can add a fairly simple method to the Dispatcher (or elsewhere, if needed) to subscribe 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'd use as follows:

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

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