Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Autofac: Hiding multiple contravariant implementations behind one composite

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?

like image 760
Steven Avatar asked Aug 21 '11 15:08

Steven


2 Answers

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:

public class CustomerMovedEventHandler
    : IEventHandler<CustomerMovedEvent>
{
    public static int handleCount = 0;
    public void Handle(CustomerMovedEvent e) { handleCount++; }
}

Here's a passing test that demonstrates that the events are going where they should:

var builder = new ContainerBuilder();

builder.RegisterSource(new Autofac.Features
    .Variance.ContravariantRegistrationSource());

builder.RegisterAssemblyTypes(typeof(IEventHandler<>).Assembly)
    .AsClosedTypesOf(typeof(IEventHandler<>));

builder.RegisterGeneric(typeof(EventRaiser<>))
    .As(typeof(IEventRaiser<>));

var container = builder.Build();

Assert.AreEqual(0, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(0, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(0, CustomerMovedAbroadEventHandler.handleCount);

container.Resolve<IEventRaiser<CustomerMovedEvent>>()
    .Raise(new CustomerMovedEvent());

Assert.AreEqual(1, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(1, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(0, CustomerMovedAbroadEventHandler.handleCount);

container.Resolve<IEventRaiser<CustomerMovedAbroadEvent>>()
    .Raise(new CustomerMovedAbroadEvent());

Assert.AreEqual(2, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(2, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(1, CustomerMovedAbroadEventHandler.handleCount);

container.Resolve<IEventRaiser<SpecialCustomerMovedEvent>>()
    .Raise(new SpecialCustomerMovedEvent());

Assert.AreEqual(3, CustomerMovedEventHandler.handleCount);
Assert.AreEqual(3, NotifyStaffWhenCustomerMovedEventHandler.handleCount);
Assert.AreEqual(1, CustomerMovedAbroadEventHandler.handleCount);

You can see I'm using an IEventRaiser<TEvent> instead of a composite IEventHandler<TEvent>. Here's how it looks:

public interface IEventRaiser<TEvent>
{
    void Raise(TEvent e);
}

public class EventRaiser<TEvent> : IEventRaiser<TEvent>
{
    List<IEventHandler<TEvent>> handlers;

    public EventRaiser(IEnumerable<IEventHandler<TEvent>> handlers)
    {
        this.handlers = handlers.ToList();
    }

    public void Raise(TEvent e)
    {
        handlers.ForEach(h => h.Handle(e));
    }
}

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 interface IEventRaiser 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 that IEventHandler interface. I shouldn't be asking anyone to Handle my event. All I want to do is Raise 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 or ICustomerRepository. 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 (the EventRaiser) 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 the IEventHandler (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.

like image 59
default.kramer Avatar answered Sep 17 '22 12:09

default.kramer


+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:

builder.RegisterSource(new ContravariantRegistrationSource());

builder.RegisterAssemblyTypes(...)
    .As(t => t.GetInterfaces()
        .Where(i => i.IsClosedTypeOf(typeof(IEventHandler<>)))
        .Select(i => new KeyedService("handler", i)));

builder.RegisterGeneric(typeof(MultipleDispatchEventHandler<>))
    .As(typeof(IEventHandler<>))
    .WithParameter(
         (pi, c) => pi.Name == "handlers",
         (pi, c) => c.ResolveService(
             new KeyedService("handler", pi.ParameterType)));
like image 42
Nicholas Blumhardt Avatar answered Sep 17 '22 12:09

Nicholas Blumhardt