Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using RegisterInitializer to wire event handlers

I have a WCF service that uses Simple Injector for dependency injection. I want to wire up some event handlers in the container bootstrapper. I have created an interface IStatusChangeNotification:

public interface IStatusChangeNotification
{
    event EventHandler<int> JobStatusChange;
}

My CommandHandler implements IStatusChangeNotification and there are two event handler classes EmailNotification and MmrNotification, each defining a Notify() method. Then in my bootstrap code I have the following:

container.Register<EmailNotification>();
container.Register<MmrNotification>();

container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
                                     Assembly.GetExecutingAssembly());

container.RegisterInitializer<IStatusChangeNotification>(scn => 
    {
        scn.JobStatusChange += container.GetInstance<EmailNotification>().Notify;
        scn.JobStatusChange += container.GetInstance<MmrNotification>().Notify;
    });

This works and the notifications are received. My question is whether this is the correct/best approach for wiring up event handlers? How do I remove the handlers at the end of the request and will failing to remove them result in a memory leak?

like image 446
David Clarke Avatar asked Mar 20 '23 06:03

David Clarke


1 Answers

Although your approach might work, I think this part of your system design might deserve the same amount of attention as your command handlers do. The most common reason for command handlers to trigger events, is to publishing events that describe some business related action. So instead of using .NET events, model those domain events the same way as you model your commands:

// Abstractions
public interface IEventHandler<TEvent> where TEvent : IDomainEvent {
    void Handle(TEvent e);
}

public interface IEventPublisher {
    void Publish<TEvent>(TEvent e) where TEvent : IDomainEvent;
}

// Events
public class JobStatusChanged : IDomainEvent {
    public readonly int JobId;
    public JobStatusChanged(int jobId) {
        this.JobId = jobId;
    }
}

// Container-specific Event Publisher implementation
public class SimpleInjectorEventPublisher : IEventPublisher {
    private readonly Container container;
    public SimpleInjectorEventPublisher(Container container) {
        this.container = container;
    }

    public void Publish<TEvent>(TEvent e) {
        var handlers = container.GetAllInstances<IEventHandler<TEvent>>();
        foreach (var handler in handlers) {
            hanlder.Handle(e);
        }
    }
}

With the previous infrastructure, you can create the following event and command handlers:

// Event Handlers
public class EmailNotificationJobStatusChangedHandler
    : IEventHandler<JobStatusChanged> {
    public void Handle(JobStatusChanged e)  {
        // TODO: Implementation
    }
}

public class MmrNotificationJobStatusChangedHandler
    : IEventHandler<JobStatusChanged> {
    public void Handle(JobStatusChanged e)  {
        // TODO: Implementation
    }
}

// Command Handler that publishes 
public class ChangeJobStatusCommandHandler : ICommandHandler<ChangeJobStatus> {
    private readonly IEventPublisher publisher;
    public ChangeJobStatusCommandHandler(IEventPublisher publisher) {
        this.publisher = publisher;
    }

    public void Handle(ChangeJobStatus command) {
        // change job status
        this.publisher.Publish(new JobStatusChanged(command.JobId));
    }
}

Now you can register your command handlers and event handlers as follows:

container.RegisterManyForOpenGeneric(typeof(ICommandHandler<>),
    Assembly.GetExecutingAssembly());

// This registers a collection of eventhandlers with RegisterAll,
// since there can be multiple implementations for the same event.
container.RegisterManyForOpenGeneric(typeof(IEventHandler<>),
    container.RegisterAll,
    Assembly.GetExecutingAssembly());

This removes the need to register each event handler class seperately, since they are simply implementations of IEventHandler<JobStatusChanged> and can all be batch-registered in one line of code. There's also no need to use RegisterInitializer to hook any events using custom defined interfaces.

Other advantages of this are:

  • The dependency between a command handler and the IEventPublisher interface makes it very clear that this command is publishing events.
  • The design is much more scalable, since its less likely for the composition root to have to change when new commands and events are added to the system.
  • It does your domain much good, since each event gets its own entity in the system.
  • It will be much easier to change the way events are processed, since that's now an implementation detail of the SimpleInjectorEventProcessor. For instance, you can deside to run them in parallel, run them in their own transaction, process them later (by storing them in an event store).
like image 68
Steven Avatar answered Apr 02 '23 16:04

Steven