RabbitMq with EasyNetQ Tutorial



I've been playing with EasyNetQ recently as a gentle introduction to RabbitMQ. Increasingly we seem to be thinking what our codebase would look like if implementted using message queues. Our hope is that it will make implementing lots of simple systems even easier.

The Video - (the written tutorial is below :)

Before we Begin, be sure you've got RabbitMQ

I've even made a little video on how to install RabbitMQ with the Management Plugin on Windows.

The source code for this tutorial is on GitHub to make life easier :)

You'll also need Visual Studio (i'm using vs2012) etc.

The Domain

We're going to be making a very simple ordering system for our fictional company IWantATesla.com in the tradition of all modern web companies we'll be implementing a RESTish API that will receive an HTTP request and then Publish an Order for a Tesla Car. Since demand is likely to be so high we might have to have load balance between various Subscribers who will then be able process the order which might involve considerable work. From the API client perspective, we don't have to wait for all the processing to happen we can have a quick response that just acknowledges the order was received and then we can go about are lives knowing at somepoint soonish we'll probably get a receipt or order confirmation email or something.

Step 1 - New WebApi Project

Create a new Solution (we're gong to need several projects for this). Create a new ASP.NET MVC 4 Web Application 

of the WebApi variety for good measure.

Step 2 - Install EasyNetQ

Right easy bit nice and simple

Step 3 - Create a Class Library Project

Ok this might seem stupid, but in order (pun?) to ultimately share a message between our WebApi project which will be our Publisher and a console app (which we'll get to shortly) which will be our subscriber we need to have a common type that both can understand. EasyNetQ in it's opinionated way thinks we need to at a minimum use a .Net type as a message and I'm inclined to agree. So what we're going to do is create a very minimumal Class  Library Project called IWantATesla.Messages and then reference from within our WebApi project and then our console app subscriber. This project will have 1 class (I said it was minimal) called TeslaOrder and it looks like this.

 

namespace IWantATesla.Messages
{
	public class TeslaOrder
	{
		public string CustomerEmail { get; set; }
		public string Model { get; set; }
		public string Color { get; set; }
	}
}

Step 4 - Create a new ApiController

Now back to the WebApi project, firstly reference the IWantATesla.Messages project you just created above. Next we create a new ApiController  below is some code. If we create a controller and inherit from ApiController like the below OrderController it'll by default correspond to the path http://localhost:portnumber/api/order where order corresponds to OrderController. In our simple code below we're only going to handle the POST response where we hand back a HttpResponseMessage which is always Created (we hardcoded that in).

using System.Net;
using System.Net.Http;
using System.Web.Http;
using EasyNetQ;
using IWantATesla.Messages;

namespace IWantATesla.WebApi.Controllers
{
	public class OrderController : ApiController
	{
		// POST api/values
		public HttpResponseMessage Post([FromBody] TeslaOrder order)
		{
			var messageBus = RabbitHutch.CreateBus("host=localhost");

			using(var publishChannel = messageBus.OpenPublishChannel())
			{
				publishChannel.Publish(order);
			}
			return Request.CreateResponse(HttpStatusCode.Created);
		}		 
	}
}

But there's some magic EasyNetQ stuff in here to. You'll note we create a RabbitHutch.CreateBus("host=localhost"), just the name rabbit hutch always makes me smile. So it's here that we're connecting to our running RabbitMQ server (please make sure it's up and running), but it's very simple. We realy on WebApi magic to bind the TeslaOrder from the POST request that comes in and then we simply, have a using() statement (the publish channel must be disposed afterwards), and we call  messageBus.OpenPublishChannel() what this does is create an Exchange on RabbitMQ called IWantATesla_Messages_TeslaOrder:IWantATesla_Messages and then we call pubishChannel.Publish(order) which sends the message to the RabbitMQ Exchange.

Step 5 - Create a Console App Project called OrderProccessor

Yes I'm afraid we need another project. As above you'll need to use Nuget to reference EasyNetQ and you'll also want add a project reference to IWantATesla.Messages. Once you've done that we're ready to write our rediculously simple subscriber.

Actually we're not going to do the absolute simplest thing we're going to do a little bit more just to make our subscribers output a little less cluttered. Lets start with the code below:

using System;
using EasyNetQ;
using IWantATesla.Messages;

namespace OrderProcessor
{
	class Program
	{
		static void Main(string[] args)
		{
		    var logger = new LogNothingLogger();
			var bus = RabbitHutch.CreateBus("host=localhost",reg=>reg.Register<IEasyNetQLogger>(log=>logger));

			bus.Subscribe<TeslaOrder>("my_subscription_id", msg =>
			{
				Console.WriteLine("Processing Order: {0} -- Model: {1} -- Color: {2}",
                    msg.CustomerEmail,
                    msg.Model,
                    msg.Color);				
			});
		}
	}

    public class LogNothingLogger : IEasyNetQLogger
    {
        public void DebugWrite(string format, params object[] args)
        {
        }

        public void InfoWrite(string format, params object[] args)
        {
        }

        public void ErrorWrite(string format, params object[] args)
        {
        }

        public void ErrorWrite(Exception exception)
        {
        }
    }
}

So first off you'll see there's 2 classes here, the first is our Console app called Program and the second is the LogNothingLogger which doesn't do anything as the name and code imply.

If you look at the Program class you can see we construct our empty logging class and call if logger. We then as with the Publish WebApi above create a RabbitHutch.CreateBus only this time in addition to the connection string to our locally running RabbitMQ instance we pass in lambda that says we want to Register<IEasyNetQLogger>(log=>logger) which just says that we want to use our LogNothingLogger class for logging instead of the default one. The default one writes lots of debugging output to the stdout which of course is the same as what our subscriber console app will write to so to mimimise confusion we override it doing nothing and have a nice clean subscriber console output.

So moving on, you can se we call bus.Subscribe<TeslaOrder>("my_subscription_id", Action<TeslaOrder>) where my_subscription_id gets appended to the name of a queue so if we look at the Queues in RabbitMQ you'll see a queue called IWantATesla_Messages_TeslaOrder:IWantATesla_Messages_my_subscription_id. For the Action<TeslaOrder> you'll see another lambda where we expect to be given a TeslaOrder and then we simply Console.WriteLine it out to the stdout. That's it for the subscriber now lets see if it all works.

Step 6 - Multiple Startup Project

Just to make this easy if you right click on the solution and do properties you'll get the below dialog. If you change the action of the WebApi project and the OrderProcessor console app both to start you'll get them both fire up at the same time to make things easy.

Then run all the things...

Step 7 - Post an Order

So at this point you should have a WebApi app up and running (though without having implented GET it's hard to know. Install a plugin like Dev HTTP Client to give your endpoint a poke.

you should also have an empty console app sitting there to. Ok lets give that endpoint a prod. We're going to use a Form Post like below.

make sure you set it to POST and set the Content-Type to application/x-www-form-urlencoded you should of-course probably prefer json or xml but just to keep things simple.

Notice the C# code always returns created even though it doesn't do anything, but if you were to write something else you'd find the WebApi is kindly doing the deserialisation for you.

Not only should you get a 201 Created back but you should also see:

That's it you've done it, you've successfully got a REST service to put messages on a RabbitMQ queue using EasyNetQ which are then processed by separate apps that simply subscribe to the the queue. Fantastic.

Step 8 - Little Bonus of RabbitMQ

Here's a nice little feature of RabbitMQ if you just go to the bin\Debug folder and fire up another instance of the OrderProcessor console app (the subscriber) and then change the color in the Post app and post again, then repeat, you'll start to notice that RabbitMQ automatically round-robins the requests between the subscribers. So in essence free load-balancing which could be very handy especially if you consider how popular Tesla's seem to be (I wonder how long the real queue for a Tesla Model S is).

Conclusion

That's it really, there's a few things I should point out, when we created the bus in the WebApi OrderController we would probably prefer to have it injected in using an IOC container. I believe everytime you do this it creates another connection to RabbitMQ and keeps it open so prehaps a singleton for it. that's all thanks for now and be sure to checkout the EasyNetQ website and GitHub project (the documentations really good) not to mention the RabbitMQ site which is also really good.


blog comments powered by Disqus

Search

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2014