Routemeister is meant to only provide you with a solution to define messages and message handlers and assist with dynamic in-process routing. You can of course combine it with something like ActiveMQ or RabbitMQ. Given this, the scope of Routemeister is not that big. Just solve one use-case. And solve it well. Current version is v0.2.0
and from here on there will be improvements and not so much expansion in features.
Just solve one use-case. And solve it well.
This post came up as an answer to a question on GitHub. You can read it here.
As of now Routemeister allows for asynchronous message handlers without responses. This works well in a send-and-forget or publish-and-forget messaging scenario. While request response is something that might be added if needed. Further extensions (if needed) will be the ability of hooking in message transformers, de-duplicators etc. Also, if requested. Support for other environments can be added. Currently it's for .Net4.5+ and no .Net Core or anything. But, again. It will be driven upon feature requests.
I don't really know that much about MediatR. But as it's from Jimmy Bogard, I'm more than certain that it's awesome. Since I got the question, I figured that I at least had to try MediatoR. I have not invested a lot of time with it, so chances are I'm doing something wrong. Just skimmed the docs to get a comparison running.
Quick compare
MediatR seems more advanced compared to current version of Routemeister (v0.2.0). It has the concept of allowing for synchronous and asynchronous messaging. As well as request and response. It has support for using CancellationTokens
and it targets more platforms.
Routemeister is intentionally asynchronous. More and more of I/O dependent utilities/modules/components/whatever, are defining an asynchronous API, and using Routemeister you will most likely interact with these kind of dependencies.
Routemeister is intentionally currently targeting "fat .Net". Why? Well that's where I have had the needs so far. This lets me make use of optimizations like IL Emits
(other blog post). Switching platforms, would mean that another factory will have to be used. E.g. one that uses compiled expressions instead.
MediatR currently supports more platforms and synchronous messaging as well as request-response messaging pattern.
Impact on your design - handlers
Routemeister has the goal of trying to stay out of the way when it comes to affecting the design of your code.
In Routemeister, you are in control of designing the interface of how an message handler should look.
So there's no predefined interface. You have to design it. Name it what you want. Name the method what you want. Of course there are restrictions. Like it shall have one method only. The method should return a Task
. The method should take one arbitrary argument only. A sample could be:
public interface IMyArbitraryHandler<in TMessage> {
Task SomeNameAsync(TMessage message);
}
//OR a more sane suggestion
public interface IMessageHandler<in TMessage> {
Task HandleAsync(TMessage message);
}
MediatR seem to rely on a set of predefined interfaces:
INotificationHandler<in TMessage>.Handle(TMessage) : void
IAsyncNotificationHandler<in TMessage>.Handle(TMessage) : Task
IRequestHandler<in TRequest, TResponse>.Handle(TRequest) : TResponse
IAsyncRequestHandler<in TRequest, TResponse>.Handle(TRequest) : Task<TResponse>
ICancellableAsyncNotificationHandler<in TMessage>.Handle(TMessage, CancellationToken) : Task
IAsyncRequestHandler<in TRequest, TResponse>.Handle(TRequest) : Task<TResponse>
ICancellableAsyncRequestHandler<in TRequest, TResponse>.Handle(TRequest, CancellationToken) : Task<TResponse>
Why it has chosen to break the convention of naming async methods with a suffix of Async
beats me.
MediatR demands specific message handler intefaces. In Routemeister you can pick your semantic naming yourself for the message handler interfaces.
Impact on your design - messages
MediatR require your messages to implement certain interfaces. E.g. the IAsyncNotificationHandler
defines a constraint on TMessage
to implement IAsyncNotification
. There are of course more, like: IRequest<TResponse>
, INotification
, IAsyncRequest<TResponse>
...
Routemeister, again tries to stay out of your way by looking at the message handler method contract. What ever is used in there as the argument, is the message. So...
Routemeister does not really care about your message. There are no interfaces or base-classes.
Construction of the message handler classes
Both Routemeister and MediatR allows you to inject a resolver for how the classes that implements your message handlers should be constructed.
Routemeister relies on the definition of a simple: Func<Type, object>
.
MediatR allows for a definition of resolving one vs many instances:
SingleInstanceFactory
which maps well toFunc<Type, object>
MultiInstanceFactory
which maps toFunc<Type, IEnumerable<object>
Haven't really seen the need for that in Routemeister yet.
Performance
This is the most dangerous thing you can do. Putting up comparisons of performance that is. But I'll get the question sooner or later so...
In Routemeister there's a small Console application that I use to create some simple timings. Now, of course you should use something that measures memory and CPU etc. as well.
In this console application, I just installed MediatR and tried to produce a similar messaging scenario. This of course had me implementing more stuff on my messaging handler infrastructure.
The version (commit) of Program.cs
containing that test code that I added MediatR to.
Routemeisters model:
public class Message { }
public interface IMyHandlerOf<in T>
{
Task HandleAsync(T message);
}
public class SampleHandler : IMyHandlerOf<Message>
{
public Task HandleAsync(Message message)
{
return Task.FromResult(0);
}
}
MediatR model:
public class Message : IAsyncNotification { }
public class SampleHandler : IAsyncNotificationHandler<Message>
{
public Task Handle(Message notification)
{
return Task.FromResult(0);
}
}
these could of course be combined, and that's what I used for the tests.
Combined model:
public class Message : IAsyncNotification { }
public interface IMyHandlerOf<in T>
{
Task HandleAsync(T message);
}
public class SampleHandler : IMyHandlerOf<Message>, IAsyncNotificationHandler<Message>
{
public Task HandleAsync(Message message)
{
return Task.FromResult(0);
}
public Task Handle(Message notification)
{
return Task.FromResult(0);
}
}
The code for the timings of MediatR:
var mediatorSharedHandler = new Mediator(
type => handler,
type => new[] { handler });
Time("MediatR - New handler", numOfCalls, mediatorSharedHandler.PublishAsync);
var mediatorNewHandler = new Mediator(
type => new SampleHandler(),
type => new[] { new SampleHandler() });
Time("MediatR - Shared handler", numOfCalls, mediatorNewHandler.PublishAsync);
Result
Again, take this with some "pinch of salt". And this only measures timings.
Test environment: Intel(R) Core(TM) i7-4790K CPU |Quad core 4.00GHz | 64GB RAM
===== Pure C# - Shared handler =====
1,49713333333333ms / 100000calls
1,49713333333333E-05ms / call
===== Pure C# - New handler =====
1,45906666666667ms / 100000calls
1,45906666666667E-05ms / call
===== Routemeister - Shared handler =====
12,187ms / 100000calls
0,00012187ms / call
===== Routemeister - New handler =====
12,0015666666667ms / 100000calls
0,000120015666666667ms / call
===== Routemeister manual Route - Shared handler =====
1,6839ms / 100000calls
1,6839E-05ms / call
===== Routemeister manual Route - New handler =====
1,7072ms / 100000calls
1,7072E-05ms / call
===== MediatR - New handler =====
254,4371ms / 100000calls
0,002544371ms / call
===== MediatR - Shared handler =====
259,355333333333ms / 100000calls
0,00259355333333333ms / call
lets put them aside:
===== Routemeister - Shared handler =====
12,187ms / 100000calls
0,00012187ms / call
===== Routemeister - New handler =====
12,0015666666667ms / 100000calls
0,000120015666666667ms / call
===== MediatR - New handler =====
254,4371ms / 100000calls
0,002544371ms / call
===== MediatR - Shared handler =====
259,355333333333ms / 100000calls
0,00259355333333333ms / call
Again. Remember. MediatR has more features and targets more platforms which could be reasons for the above numbers. I can also have misused it since I didn't spend that much time on learning it. Hope I have not stepped on anyone's toes.
Cheers,
//Daniel