danielwertheim

danielwertheim


notes from a passionate developer

Share


Sections


Tags


Disclaimer

This is a personal blog. The opinions expressed here represent my own and not those of my employer, nor current or previous. All content is published "as is", without warranty of any kind and I don't take any responsibility and can't be liable for any claims, damages or other liabilities that might be caused by the content.

Introducing Routemeister

Routemeister is a small NuGet built with one single purpose. Assisting with in-process async message routing. It can be used if you e.g. are dispatching messages cross process using RabbitMQ or ActiveMQ and then want to dispatch the message to typed handlers within the consuming process.

For latest up to date info, visit the project page at GitHub.

Usage

First, install it. It's distributed via NuGet.

install-package routemeister

Now, you need to define a message handler marker interface. The requirements are:

//This interface is something you define and
//not part of RouteMeister.
public interface IHandle<T>
{
    Task HandleAsync(T message);
}

Now, create some actual handlers (classes implementing your interface) that is supposed to process your messages. Each class can naturally implement the interface multiple times to handle different types of messages.

//Implement your interface multiple times
//to handle many types of messages
public class MyHandler :
    IHandle<MyConcreteMessage>,
    IHandle<ISomeInterfaceMessage>
{
    public Task HandleAsync(MyConcreteMessage message)
    {
        //Do something with the message
    }

    //You can route messages as interfaces as well
    public Task HandleAsync(ISomeInterfaceMessage message)
    {
        //Do something with the message
    }
}

Each message type being processed can (if you want) be processed by multiple message handlers (different classes).

//You can have many classes handling the message types
//by implementing your interface in yet another class
public class SomeOtherHandler :
    IHandle<MyConcreteMessage>,
    IHandle<ISomeInterfaceMessage>
{
    public Task HandleAsync(MyConcreteMessage message)
    {
        //Do something with the message
    }

    public Task HandleAsync(ISomeInterfaceMessage message)
    {
        //Do something with the message
    }
}

Now, lets get the routes constructed. A route is represented by MessageRoute. Each message route has a MessageType and one-to-many Actions. Each Action represents a message handler.

To get the message routes, you use the MessageRouteFactory. To the factory, you pass a Func<Type, object> that is responsible for creating an instance of the message handler class. Why? Well, you could pass Activator.CreateInstance but you probably want to use your IoC container or something, so that additional dependencies are resolved via the container.

//Using Activator
var factory = new MessageRouteFactory(
    type => Activator.CreateInstance(type));

//Using Ninject
var factory = new MessageRouteFactory(
    type => kernel.Get(type));

The factory needs to know in what assemblies to look for message handlers. It also needs to know which interface is used as the marker interface.

MessageRoutes routes = factory.Create(
    typeof(SomeType).Assembly,
    typeof(IHandle<>));

Now, the routes can be used as you want to route messages manually or using e.g. SequentialAsyncMessageRouter.

var router = new SequentialAsyncMessageRouter(routes);
await router.RouteAsync(new MyConcreteMessage
{
    //Some data
}).ConfigureAwait(false);

or manually

var message = new MyConcreteMessage
{
    //Some data
};
var messageType = message.GetType();
var route = routes.GetRoute(messageType);
foreach (var action in route.Actions)
    await action(message).ConfigureAwait(false);

That's it. Use if it helps. Contribute if you have any ideas for improvement.

Cheers,

//Daniel

View Comments