Tuesday, February 04, 2014

EasyNetQ: A Layered API

I had a great discussion today on the EasyNetQ mailing list about a pull request. It forced me to articulate how I view the EasyNetQ API as being made up of distinct layers, each with a different purpose.

EasyNetQ_API

EasyNetQ is a collection of components that provide services on top of the RabbitMQ.Client library. These do things like serialization, error handling, thread marshalling, connection management, etc. They are composed by a mini-IoC container. You can replace any component with your own implementation quite easily. So if you’d like XML serialization instead of the built in JSON, just write an implementation of ISerializer and register it with the container.

These components are fronted by the IAdvancedBus API. This looks a lot like the AMQP specification, and indeed you can run most AMQP methods from this API. The only AMQP concept that this API hides from you is channels. This is because channels are a confusing low-level concept that should never have been part of the AMQP specification in the first place. ‘Advanced’ is not a very good name for this API to be honest, ‘Iamqp’ would be much better.

Layered on top of the advanced API are a set of messaging patterns: Publish/Subscribe, Request/Response, and Send/Receive. This is the ‘opinionated’ part of EasyNetQ. It is our take on how such patterns should be implemented. There is very little flexibility; either you accept our way of doing things, or you don’t use it. The intention is that you, the user, don’t have to expend mental bandwidth re-inventing the same patterns; you don’t have to make choices every time you simply want to publish a message and subscribe to it. It’s designed to achieve EasyNetQ’s core goal of making working with RabbitMQ as easy as possible.

The patterns sit behind the IBus API. Once again, this is a poor name, it’s got very little to do with the concept of a message bus. A better name would be IPackagedMessagePatterns.

IBus is intended to work for 80% of users, 80% of the time. It’s not exhaustive. If the pattern you want to implement is not provided by IBus, then you should use IAdvancedBus. There’s no problem with doing this, and it’s how EasyNetQ is designed to be used.

I hope this explains the design philosophy behind EasyNetQ and why I push back against pull requests that add complexity to the IBus API. I see the ease-of-use aspect of EasyNetQ as its most important attribute. RabbitMQ is a superb piece of infrastructure and I want as many people in the .NET community to use it as possible.

1 comment:

Alex said...

I really enjoy looking through your API it's very robust and simple. The one thing that I'd like to see in the future however is a better way to handle and resend messages that have had unhandled exceptions occur during the message handling.

I actually wrote my own wrapper around RabbitMQ and actually manage exceptions similar to you up to a certain point. I use the retry property on the message as well to try at least once, but then I throw it in an Exception exchange/queue similar to you. I have a service subscribing to that queue that then places it in a database.

Now at this point we can do whatever we need with our messages. All metadata regarding the message, queue, etc are stored as well. After we have either fixed the problem or the message data, I resend the message to a "ResendException" topic exchange that every queue binds to with the queues name as the routing key for the messages. The exception message is then reposted to that queue and routed properly as we define the routing key to be the queue that it originated from. Thus no messages go to a queue that had already received and processed that message properly. Only the queue that failed to handle that message gets the message back. Now, i'm not sure if this is something that you can add to your framework, specifically the service to listen to that error queue, but it may be possible to setup the Exception Exchange and the Resend Exchange bindings and the consumer of your API create what it needs to handle those exceptions and to repost them to the resend exchange.

Anyways, I love your api and I have used it as reference a little bit when coding my own. So thank you for that!

Alex