Quantcast
Channel: try-catch-FAIL - .NET
Viewing all articles
Browse latest Browse all 14

RageFeed’s Message Bus

$
0
0

In my recent post about heavy controller actions in RageFeed, I promised to show more details about how the Message Bus pattern was being employed within RageFeed to facilitate the creation of simple, business-logic-free controllers.  Then I got sidetracked with a new job, CodeStock, travel, and life in general. Since then, the message bus in RageFeed has changed quite a bit.  In this post, I’ll show you how the bus looks today and how it’s implemented. 

As a refresher, recall that the controllers within RageFeed were becoming quite bloated, with code resembling the following:

private readonly IRageFeedMembershipProvider _membershipProvider;
private readonly IEmailDispatcher _emailDispatcher;
private readonly IProfileService _profileService;
private readonly IMembershipService _membershipService;

public SignupController(IRageFeedMembershipProvider membershipProvider, 
    IEmailDispatcher emailDispatcher, 
    IProfileService profileService, 
    IMembershipService membershipService)
{
    _membershipProvider = membershipProvider;
    _emailDispatcher = emailDispatcher;
    _profileService = profileService;
    _membershipService = membershipService;
}
...
public ViewResult Activate(string username, string activationToken)
{
    var profile = _profileService.GetForUser(username);

    if (profile == null || profile.ActivationToken != activationToken)
    {
        return View("ActivationFailed");
    }

    var user = _membershipService.GetUser(username);
    user.IsApproved = true;
    _membershipService.UpdateUser(user);

    return View();
}

By applying the message bus pattern, I was able to simplify the controllers to code that looked more like this:

private readonly IBus _bus;
private readonly IAuthenticationService _authService;

public SignupController(IBus bus, IAuthenticationService authService)
{
    _bus = bus;
    _authService = authService;
}
...
public ViewResult Activate(string username, string activationToken)
{
    var result = _bus.RequestReply<ActivateUserRequest,ActivateUserReply>(new ActivateUserRequest { Username = username, ActivationToken = activationToken });

    if (!result.Succeeded)
    {
        return View("ActivationFailed");
    }

    _authService.SetAuthCookie(username);

    return View();
}

While this isn’t a huge simplification in terms of lines of code, the improvements are still significant.  The business logic is gone and safely encapsulated somewhere on the far side of the bus.  Prior to the change, the tests had to setup expectations on mocks for both the profile service and the membership service.  After the change, the tests only need to setup the appropriate response from the bus.

The bus itself is quite simple:

public class MessageBus : IBus
{
    private readonly IMessageHandlerRegistry _registry;

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

    /// <summary>
    /// Sends a message that doesn't require a response.
    /// </summary>
    /// <param name="message"></param>
    /// <typeparam name="TMessage"></typeparam>
    public void Send<TMessage>(TMessage message)
    {
        var handler = _registry.GetCommandHandlerFor<TMessage>();

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

        handler.Handle(message);
    }

    /// <summary>
    /// Sends a message and gets the reply. 
    /// </summary>
    /// <typeparam name="TRequest"></typeparam>
    /// <typeparam name="TReply"></typeparam>
    /// <param name="request"></param>
    /// <returns></returns>
    public TReply RequestReply<TRequest, TReply>(TRequest request)
    {
        var handler = _registry.GetCommandHandlerFor<TRequest,TReply>();

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

        return handler.Handle(request);
    }
}

It uses a registry to find appropriate command handlers, then dispatches the incoming message to the handler.  For request/reply scenarios, it returns the reply from the handler back to the original caller.  The registry is nothing more than a wrapper around StructureMap:

public class MessageHandlerRegistry : IMessageHandlerRegistry
{
    private readonly IContainer _container;

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

    public ICommandHandler<TMessage> GetCommandHandlerFor<TMessage>()
    {
        return _container.GetInstance<ICommandHandler<TMessage>>();
    }

    public IMessageHandler<TRequest, TReply> GetCommandHandlerFor<TRequest, TReply>()
    {
        return _container.GetInstance<IMessageHandler<TRequest, TReply>>();
    }
}

Note: I don’t like the names ICommandHandler and IMessageHandler, they’re going to be renamed soon(ish).

By leveraging StructureMap, command handlers can be automagically registered for the appropriate request/reply types, so adding and subscribing a command handler becomes as simple as creating a class that implements an appropriate interface:

public class CheckEmailAvailabilityHandler : IMessageHandler<CheckEmailAvailabilityRequest, CheckEmailAvailabilityReply>
{
    private readonly IUserRepository _repository;

    public CheckEmailAvailabilityHandler(IUserRepository repository)
    {
        _repository = repository;
    }

    public CheckEmailAvailabilityReply Handle(CheckEmailAvailabilityRequest request)
    {
        var isInuse = _repository.GetByEmail(request.Email) != null;

        return new CheckEmailAvailabilityReply {IsInUse = isInuse};
    }
}

Organizing the business logic this way makes it far easier to create small, reusable, easily-testable, and loosely-coupled nuggets of logic. 

One final note, I’m trying to follow the principles of the “onion architecture” as well as the “folder-per-feature” used by Ayende.  This means that each major feature in RageFeed will have it’s own folder (namespace) within Core, as illustrated below.

FeatureOrganization

I’m still not completely satisfied with this breakdown.  It feels “wrong” to have the messages living side-by-side with the handlers, but according to the onion architecture, it’s fine for the web layer to depend on core.  Even so, I’m considering breaking the message off into a separate namespace or project, but it does make it easier to find what you’re looking for when everything related to the implementation of a feature is contained in a single folder as it is now. If you have suggestions for better ways to organize things, please do share.

If you are familiar with MVCContrib, you should be wondering why I chose to implement my own bus instead of leveraging the bus that’s available in MVCContrib.  I have good reasons for that, but that’s a topic for another day.  In a future post (to be published sometime before the dinosaurs return in 2012), I’ll compare the RageFeed bus with the MVCContrib bus and highlight the pros and cons of each.  Until then, let me know what you think in the comments!


Viewing all articles
Browse latest Browse all 14

Trending Articles