Dec 5 2010

RageFeed’s Application Bus, and Why I Built My Own

Category: Best PracticesMatt @ 13:32

In my last article, I introduced the Application Bus pattern, a specialization of the  Message Bus pattern.  I’m employing an Application Bus in RageFeed, the hobby social networking application I sometimes work on.  Today, I’ll show you how the bus utilizes StructureMap for locating message end points and for dispatching messages.  I’ll also explain why I chose to build my own bus instead of leveraging an existing one, such as the bus available in MVCContrib

The RageFeed Application Bus

I showed some of the interfaces from the RageFeed bus last time.  It’s pretty barebones, but it supports both one-way and two-way messaging patterns.  In one-way messaging, the sender does not require any sort of response or acknowledgement.  In two-way messaging (also known as request-reply messaging), the sender sends a message, then waits for a response to be sent back by the message’s handler.

image

Classes that want to send messages over the bus simply use the IBus interface, while classes that want to receive messages implement one of the two handler interfaces.   The registry interface is used by the concrete implementation of IBus to locate message handlers. 

IBus is implemented by MessageBus.  It’s a trivial implementation, but it has met every requirement we’ve had so far:

public class MessageBus : IBus
{
    private readonly IMessageHandlerRegistry _registry;

    public MessageBus(IMessageHandlerRegistry registry)
    {
        _registry = registry;
    }

    public void Send<TMessage>(TMessage message)
    {
        var handler = _registry.GetHandlerFor<TMessage>();

        if (handler == null)
        {
            throw new HandlerNotFoundException(typeof (TMessage));
        }

        handler.Handle(message);
    }

    public TReply RequestReply<TRequest, TReply>(TRequest request)
    {
        var handler = _registry.GetHandlerFor<TRequest,TReply>();

        if (handler == null)
        {
            throw new HandlerNotFoundException(typeof (TRequest), typeof (TReply));
        }

        return handler.Handle(request);
    }
}

As you can see, the implementation really is quite simple: it receives a message, asks the registry for a suitable handler, then dispatches it to the handler.  There’s a lot of room for additional features (better error handling, exposing hooks to allow messages to be inspected and/or modified, etc), but we just haven’t had a need for those things yet.

The MessageHandlerRegistry implements (surprise!) IMessageHandlerRegistry, and its job is managing the handlers.  For this, it utilizes StructureMap’s IContainer interface:  

public class MessageHandlerRegistry : IMessageHandlerRegistry
{
    private readonly IContainer _container;

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

    public IHandle<TMessage> GetHandlerFor<TMessage>()
    {
        try
        {
        return _container.GetInstance<IHandle<TMessage>>();
        }
        catch (StructureMapException ex)
        {
            //202 means no suitable type found in the container. 
            if (ex.ErrorCode == 202)
            {
                return null;
            }
            throw;
        }
    }

    public IHandle<TRequest, TReply> GetHandlerFor<TRequest, TReply>()
    {
        try
        {
            return _container.GetInstance<IHandle<TRequest, TReply>>();
        }
        catch (StructureMapException ex)
        {
            //202 means no suitable type found in the container. 
            if (ex.ErrorCode == 202)
            {
                return null;
            }
            throw;
        }
    }
}

Again, this class is fairly simple, all the heavy lifting is provided by StructureMap. 

By convention in RageFeed, anything that implements either of the IHandle interfaces is automagically registered; there’s no need to manually register anything.  This convention is accomplished using a StructureMap registry that scans all RageFeed assemblies and wires up message handlers appropriately:

public class MessagingRegistry : Registry
{
    public MessagingRegistry()
    {
        For<IBus>().Use<MessageBus>();
        For<IMessageHandlerRegistry>().Use<MessageHandlerRegistry>();

        Scan(scanner =>
                {
                    scanner.AssembliesFromApplicationBaseDirectory(assembly => assembly.FullName.StartsWith("LazyDeveloperGroup."));
                    scanner.ConnectImplementationsToTypesClosing(typeof (IHandle<,>));
                    scanner.ConnectImplementationsToTypesClosing(typeof (IHandle<>));
                });
    }
}

The “magic,” such as it is, comes from the IAssemblyScanner.ConnectImplementationsToTypesClosing method provided by StrucutreMap.  The method connects all concrete types that implement either handler interface to the closed version of the interface.  It’s essentially doing this, but for every handler type in the entire project:

For<IHandle<LoginRequest,LoginReply>>().Use<LoginHandler>();
For<IHandle<LogoutMessage>>().Use<LogoutHandler>();
//etc...

In the end, the bus enables us to create controllers with actions like this:

public ActionResult Create(CreateForm form)
{
    if (!ModelState.IsValid)
    {
        return View("Index", form);
    }

    var request = new CreateUserRequest {Username = form.Username, Email = form.Email, Password = form.Password};
    var response = _bus.RequestReply<CreateUserRequest, CreateUserReply>(request);

    if (!response.Succeeded)
    {
        return View("Index", form);
    }

    return this.RedirectToAction(c => c.Success());
}

As I said in my previous post, message passing is the loosest form of coupling that can be achieved in software: our controllers are completely decoupled from the core domain logic, which means we can easily reuse our core domain in a Silverlight or desktop version of the app.  We can also distribute the core logic across logical or physical boundaries to increase scalability without having to modify our controllers.

“But Matt, didn’t you just reinvent the wheel?”

There are indeed open-source Application Bus implementations in the wild now, though I only know of one that has attained any sort of widespread use: the MVCContrib bus.  As RageFeed does indeed utilizes MVCContrib, it would have been quite easy to just build on MVCContrib’s bus.  However, I really don’t care for the implementation of the MVCContrib bus.  First, it requires handlers to be manually registered with the bus:

public class ApplicationBus : List<Type>, IApplicationBus
{        
    public new void Add(Type type)
    {
        if (type.GetInterface(typeof(IMessageHandler).Name) == null)
        {
            throw new InvalidOperationException(string.Format("Type {0} must implement the IMessageHandler interface",type.Name));
        }
        base.Add(type);
    }

    ...
}

This is not conducive to the zero-friction development we are attempting to maintain in RageFeed.  We could have built a little bit of infrastructure to automatically register our handlers, but that wouldn’t have addressed my next concern: that the bus must create instances of each handler in order to find a suitable handler for a message.  You can see this behavior here:

public class ApplicationBus : List<Type>, IApplicationBus
{        
    ...
    
    public void Send(IEventMessage eventMessage)
    {
        foreach (var handler in GetHandlersForType(eventMessage.GetType()))
        {
            handler.Handle(eventMessage);
        }
    }
    
    ...

    public IEnumerable<IMessageHandler> GetHandlersForType(Type type)
    {
        foreach (Type handlerType in this)
        {
            var handler = _factory.Create(handlerType);
            if (handler.CanHandle(type))
            {
                yield return handler;
            }
        }            
    }
}

If you have a lot of handlers, this could be expensive, especially if you have handlers that depend on expensive-to-create resources.  Sure, you could rework your handlers to lazily-create such resources, but that’s once again taking us farther away from zero-friction development.  I wanted a bus that just worked without requiring anyone to write code with regard to how the bus functions. 

Note: It’s worth pointing out that MVCContrib’s bus is not trying to be a general-purpose Application Bus.  It exists mainly to support MVCContrib’s Portable Areas feature. 

Bus.Send(new EndOfPostMessage());

Well, that’s it for this week’s month’s quarter’s blog post.  I’ve shown you a very barebones Application Bus implementation utilizing StructureMap.  As RageFeed develops, I expect the capabilities of the bus will grow as we find new ways to utilize it.  I’ll be sure to post more when that happens.  In the meantime, please share your questions and comments below.  Thanks!

Tags:

blog comments powered by Disqus