Elegant Domain Events Setup In Asp.Net Mvc

By Mike on 13 June 2012

I consider the Domain Events Pattern an elegant way of communicating between layers with minimum of coupling and I even coded a light library for it (Domain Events Toolkit). I'm using both the pattern and the library in Fulldot to handle some operations.


Best example is when a user submit a comment. One the comment is saved you want to notify the admin or the post author , usually via an email. Other example is the Pingback functionality, after writing a post the pingback module searches the content for urls and tries to ping them. These kinds of operations are performed as a result of a domain update.

The steps are usually these:
- Create the event and the event handler types
- register the event handler
- raise event
- event is handled
- cleanup

In a web application, since every request executes independently, the handlers make sense only for dome actions and this means you have something like this (using Domain Events Toolkit)

public class CommentSubmittedEvent:IDomainEvent
{
    /* ... implementation ... */
}

public class SendEmailAfterCommentHandler:DomainEventHandlerBase<CommentSubmittedEvent>
{
  /* constructor with dependencies, implementation */
}


//in the controller
 public ActionResult AddComment(AddCommentModel model)
        {
            var sendEmail= new SendEmailAfterCommentHandler(_repository);
			using (_events.RegisterHandler(sendEmail))
			 {
			      var cmd = Fulldot.FactoryCreate.AddCommentCommand(model, this.GetUserContext(), Request.UserHostAddress);
				  this.Handle(cmd);
			 }
			
        }

A bit of explanation is required for the controller. Aside Domain Events I also use a Command based architecture to update the domain. The first line just creates that command. The second sends the command to a command handler which will actually do the real work.  The command handler is setup in the Container, while the 'Handle' method is provided by CavemanTools Mvc. In a nutshell it will ask the Container for a handler for the command, then it will execute it.

Once the command has been executed, a new domain event is raised , which will be handled by an instance of LocalDomainEventManager (the _events variable) provided by the Domain Events Toolkit. While it's pretty clean, things will go much uglier if you want to setup more handlers for the event. For example, after changing a post, besides the Pingback I want the post processed by Lucene.Net (or another full text search service). We have 2 handlers now and the code will get uglier, all because of the setup of the handlers.

And in fairness, this is an infrastructural concern, the controller shouldn't care about the setup of the domain events manager. Yes we can delegate that to a Service and then simply call the Service method. But we can do it better, using Filter Actions.

Simply put, it looks like this

[HandleDomainEvent(typeof(NotifyCommentHandler))]
        public ActionResult AddComment(AddCommentModel model)
        {
            var cmd = Fulldot.FactoryCreate.AddCommentCommand(model, this.GetUserContext(), Request.UserHostAddress);
            this.Handle(cmd);
            return RedirectToRoute("post",new {slug="comments",id=model.PostId});
        }

Just decorate the action with the types fo the handlers. It makes it so easy to add/ remove handlers without cluttering the real controller code and the intention is much clearer.


And this is the HandleDomainEventAttribute

[AttributeUsage(AttributeTargets.Method)]
    public class HandleDomainEventAttribute:ActionFilterAttribute
    {
        private const string ContextKey = "_dispose_handlers";
        private IEnumerable<Type> _handlers;
        public HandleDomainEventAttribute(params Type[] handlers)
        {
            handlers.MustNotBeNull();
            _handlers = handlers;
        }

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            List<IDisposable> _res= new List<IDisposable>();
            var events = DependencyResolver.Current.GetService<IDomainEventsManager>();
            foreach(var tp in _handlers)
            {
                var h = DependencyResolver.Current.GetService(tp) as IHandleDomainEvent;
                _res.Add(events.RegisterHandler(h));
            }
            HttpContextRegistry.Set(ContextKey,_res);            
        }

        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var disp = HttpContextRegistry.Get<List<IDisposable>>(ContextKey);
            if (disp==null) return;
            disp.ForEach(d=>d.Dispose());

        }
    }

Half of work is done here, the other half is done by the DI Container. Every event handler is registered in the Container (which will take care of all dependencies required) then the attribute just ask for them, registers them to the domain events manager (also supplied by the Container) and after the action is finished, disposes them.

     So for the modify post case  (after which we want both the pingback and the full text search update), the controller action only gets the attribute.

[HandleDomainEvent(typeof(PingbackHandler), typeof(FTSUpdateHandler))]

One line of code, elegant and efficient.

Add comment

biuquote
Loading