I was triggered by this SO question about (.NET 4.0) covariance and contravariance support for Autofac, and now I'm trying to achieve something similar, but without any luck.
What I am trying to achieve is configure Autofac in such way that when I resolve a single concrete IEventHandler<TEvent>
(for the sake of demonstration using container.Resolve
, but normally of course using constructor injection), Autofac will return me a MultipleDispatchEventHandler<TEvent>
that wraps all registered event handlers that are assignable from the requested handler.
In other words, when I write this:
var handler = container
.GetInstance<IEventHandler<CustomerMovedEvent>>();
handler.Handle(new CustomerMovedEvent());
With respect to the application design (given below), I'd expect a MultipleDispatchEventHandler<CustomerMovedEvent>
to be returned that wraps both a CustomerMovedEventHandler
and a NotifyStaffWhenCustomerMovedEventHandler
.
Here is the application design:
// Events:
public class CustomerMovedEvent { }
public class CustomerMovedAbroadEvent : CustomerMovedEvent { }
public class SpecialCustomerMovedEvent : CustomerMovedEvent { }
// Event handler definition (note the 'in' keyword):
public interface IEventHandler<in TEvent>
{
void Handle(TEvent e);
}
// Event handler implementations:
public class CustomerMovedEventHandler
: IEventHandler<CustomerMovedEvent>
{
public void Handle(CustomerMovedEvent e) { ... }
}
public class NotifyStaffWhenCustomerMovedEventHandler
: IEventHandler<CustomerMovedEvent>
{
public void Handle(CustomerMovedEvent e) { ... }
}
public class CustomerMovedAbroadEventHandler
: IEventHandler<CustomerMovedAbroadEvent>
{
public void Handle(CustomerMovedAbroadEvent e) { ... }
}
This is the definition of the MultipleDispatchEventHandler<TEvent>
, defined in the Composition Root:
// A composite wrapping possibly multiple handlers.
public class MultipleDispatchEventHandler<TEvent>
: IEventHandler<TEvent>
{
private IEnumerable<IEventHandler<TEvent>> handlers;
public MultipleDispatchEventHandler(
IEnumerable<IEventHandler<TEvent>> handlers)
{
this.handlers = handlers;
}
public void Handle(TEvent e)
{
this.handlers.ToList().ForEach(h => h.Handle(e));
}
}
This is my current configuration:
var builder = new ContainerBuilder();
// Note the use of the ContravariantRegistrationSource (which is
// available in the latest release of Autofac).
builder.RegisterSource(new ContravariantRegistrationSource());
builder.RegisterAssemblyTypes(typeof(IEventHandler<>).Assembly)
.AsClosedTypesOf(typeof(IEventHandler<>));
// UPDATE: I'm registering this last as Kramer suggests.
builder.RegisterGeneric(typeof(MultipleDispatchEventHandler<>))
.As(typeof(IEventHandler<>)).SingleInstance();
var container = builder.Build();
With the current configuration, the application fails during the call to Resolve
, with the following exception:
Autofac.Core.DependencyResolutionException: Circular component dependency detected: MultipleDispatchEventHandler'1[[SpecialCustomerMovedEvent]] -> IEventHandler'1[[SpecialCustomerMovedEvent]][] -> MultipleDispatchEventHandler'1[[SpecialCustomerMovedEvent]].
Now the question is of course: how can I fix the configuration (or the design) to support this?
I'm going to make this a separate answer instead of modifying my other one. This one solves the example scenario without using a composite.
Working Code
I added a
static int handleCount
to each of the event handlers for testing purposes, like this:Here's a passing test that demonstrates that the events are going where they should:
You can see I'm using an
IEventRaiser<TEvent>
instead of a compositeIEventHandler<TEvent>
. Here's how it looks:Design Thoughts
Avoiding the composite
IEventHandler
sure makes our work at the composition root easier. We don't have to worry about recursive composition or making sure the composite is the default implementation. But we added a new interfaceIEventRaiser
which might look redundant. Is it? I think not.Raising an event and handling an event are two different things.
IEventHandler
is an interface that has to do with handling events.IEventRaiser
is an interface that has to do with raising events.Imagine that I'm a piece of code that wants to raise an event. If I ask the IoC for a single
IEventHandler
I am introducing coupling that I don't need. I shouldn't need to know about thatIEventHandler
interface. I shouldn't be asking anyone toHandle
my event. All I want to do isRaise
it. Handling may or may not happen on the other side; it is irrelevant to me. I'm selfish - I want an interface created solely for me and my need to raise events.As an event raiser, I intend to raise an event. As an event handler, I intend to handle an event. We have two different intents, so we should have two different interfaces. Just because we could use the same interface and a composite doesn't mean we should.
The Interface Segregation Principle seems to be more about splitting fat interfaces into thinner ones (see also Role Interface). In our case, we don't have a fat interface, but I think we're doing something similar - "Interface Segregation by Intent".
One more thing
In writing this answer I almost articulated a design idiom that I think many of us are familiar with, but I don't think we have standard terminology for it.
"Type C Interface" - frequently Consumed, rarely Implemented. A "service" interface. For example,
IEventRaiser
orICustomerRepository
. These interfaces probably have only one implementation (maybe decorated a bit) but they are consumed all over the place by code that wants to Raise Events or Save Customers."Type I Interface" - frequently Implemented, rarely Consumed. A "plugin" interface. For example,
IEventHandler<TEvent>
. Consumed in only one place (theEventRaiser
) but implemented by many classes.The same interface should not be both a Type C and a Type I. This is another reason to separate the
IEventRaiser
(Type C) from theIEventHandler
(Type I).I'm thinking that the composite pattern is only applicable to Type C interfaces.
Please edit or comment if there is standard terminology for what I've called "Type C" and "Type I" interfaces.
+1 for
IEventRaiser<T>
by @default.kramer. Just for the record, since the linked answer doesn't provide any code, and the configuration for this scenario is a bit less than intuitive because of the generic types involved: