Using Autofac with Domain Events
Asked Answered
S

2

14

I'm trying to introduce domain events into a project. The concept is described in Udi Dahan's post - http://www.udidahan.com/2009/06/14/domain-events-salvation/

Here's the domain event code

public interface IDomainEvent { }

public interface IHandleDomainEvents<T> where T : IDomainEvent
{
     void Handle(T args); 
}

public interface IEventDispatcher
{
    void Dispatch<TEvent>(TEvent eventToDispatch) where TEvent : IDomainEvent;
}

public static class DomainEvents
{
    public static IEventDispatcher Dispatcher { get; set; }

    public static void Raise<TEvent>(TEvent eventToRaise) where TEvent : IDomainEvent
    {
        Dispatcher.Dispatch(eventToRaise);
    }
}

The most important part is the IEventDispatcher implementation that decouples the domain event from whatever needs to happen when the event is raised. The trick is to wire up this coupling through a container. Here's my attempt

Code for Registering all Domain event handlers....

        var asm = Assembly.GetExecutingAssembly();
        var handlerType = typeof(IHandleDomainEvents<>);

        builder.RegisterAssemblyTypes(asm)
            .Where(t => handlerType.IsAssignableFrom(t)
                        && t.IsClass
                        && !t.IsAbstract)
            .AsClosedTypesOf(handlerType)
            .InstancePerLifetimeScope(); 

And resolving all the event handlers in the dispatcher. The problem is no handlers are resolved.

public class EventDispatcher : IEventDispatcher
{
    private readonly IContainer _container;

    public EventDispatcher(IContainer container)
    {
        _container = container;
    }

    public void Dispatch<TEvent>(TEvent eventToDispatch) where TEvent : IDomainEvent
    {
        var handlers = _container.Resolve<IEnumerable<IHandleDomainEvents<TEvent>>>().ToList();
        handlers.ForEach(handler => handler.Handle(eventToDispatch));
    }
}

So I'm not registering the event handlers correctly or not resolving them. How do I check that the registration is working?

Example code of a handler

public class SendWebQuestionToCSO : IHandleDomainEvents<JobNoteCreated>
{
    private readonly IConfig _config;

    public SendWebQuestionToCSO(IConfig config)
    {
        _config = config;
    } 

    public void Handle(JobNoteCreated args)
    {
        var jobNote = args.JobNote;
        using(var message = new MailMessage())
        {
            var client = new SmtpClient {Host = _config.SmtpHostIp};
            message.From = new MailAddress(_config.WhenSendingEmailFromWebProcessUseThisAddress);
            ...... etc
        }
    }
}

UPDATE After some trial and error the EventDispatcher is working ok! If I manually register a handler and then fire the domain event it works. The assembly scanning/registraion is my problem. The manual registration code...

builder.RegisterType<SendWebQuestionToCSO >().As<IHandleDomainEvents<JobNoteCreated>>();

So how do I scan all assemblies for all IHandleDomainEvents<> given they look like this

public class SendWebQuestionToCSO : IHandleDomainEvents<JobNoteCreated>
Sylvester answered 12/4, 2011 at 2:16 Comment(0)
V
8

As Peter pointed out, the registration problem was with your Where() clause.

When scanning assemblies Autofac filters components automatically based on the services you specify, so it would be equally correct to just use:

builder.RegisterAssemblyTypes(asm)
    .AsClosedTypesOf(handlerType)
    .InstancePerLifetimeScope();
Veranda answered 13/4, 2011 at 11:13 Comment(6)
Ah, yes, even smoother. Robbed me of 15 rep though!Trehala
Hehe like you need it, Mr. L. ;) you got my vote nonetheless.Veranda
Hey guys. I'm tring to implement this approach in my MVC5 multi-tenant application where all my IOC dependencies are per request scope. The Event Handlers are getting resolved from IContainer instead of the MVC DependencyResolver. The event handlers are then trying to resolve constructor injections which is throwing the following error:Tletski
Error: No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.Tletski
Hi guys, I try this but the EventDispatcher is not resolve. builder.RegisterType<EventDispatcher>() .AsImplementedInterfaces() .InstancePerLifetimeScope(); builder.RegisterAssemblyTypes(typeof (Repository<>).Assembly) .AsClosedTypesOf(typeof (IHandleDomainEvent<>));Dumpy
I didn't understand the DomainEvents static class. How does it connects to autofac and respect scope from the caller of Raise.Incorporator
T
2

The problem in your assembly scanning code is when you use IsAssignableFrom. The filter will ask: "could an instance of SendWebQuestionToCSO be assigned to a variable of IHandleDomainEvents<>?" The answer is obviously "no" since you can never have a variable of an open generic type.

The trick would be to inspect the interfaces implemented by each type and check whether any of them are closing the open generic interface type. Here's a revised scanner:

    var asm = Assembly.GetExecutingAssembly();
    var handlerType = typeof(IHandleDomainEvents<>);

    builder.RegisterAssemblyTypes(asm)
        .Where(t => t.GetInterfaces().Any(t => t.IsClosedTypeOf(handlerType)))
        .AsImplementedInterfaces()
        .InstancePerLifetimeScope(); 
Trehala answered 13/4, 2011 at 0:16 Comment(4)
Hi Peter, in what namespace can I find 'IsClosingTypeOf'Sylvester
Did you mean IsClosedTypeOf? I tried it, and it didn't work. However it did look very promising! Any other ideas would be very helpful I've been messing with this for far too long now...Sylvester
I've looked at the source on code.google.com hg/src/Source/Autofac/Util/ReflectionExtensions.cs and the extension method IsClosingTypeOf is very simple so I copied the code resulting in - .Where(t => t.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == handlerType)). THIS WORKS!!! The big question is where has the extension method gone in version 2.4.5.724 .Net4 ?Sylvester
Ah, yes, updated my local source now and found it. The method is now TypeExtensions.IsClosedTypeOf.Trehala

© 2022 - 2024 — McMap. All rights reserved.