Tutorial

This tutorial takes you building the Greetings project, which is Hello World via a Task Queue. The walkthrough will build the example availabe in the Examples folder of Brighter available in the public repo at Greetings Example if you want to follow along there instead of typing in the code.

Note that you will need to have RabbitMQ installed to step through this example as a tutorial.

Step One

Create a C# Console Application, targeting .NET 4.5.

Note that you can use any kind of application with Brighter.ServiceActivator, it's purpose is to take messages of a task queue and route to a Request Handler seamlessly. We use a console application as we intend to build a Windows Service which is one of the most common use cases for the Service Activator pattern.

Step Two

Install the Paramore.Brighter.ServiceActivator package from NuGet

PM> Install-Package Paramore.Brighter.ServiceActivator

This will install Paramore.Brighter.ServiceActivator and it's dependencies Paramore.Brighter, Polly, and Newtonsoft.Json.

Although the Service Activator provides support for a consumer reading messages of an Input Channel, we need to supply a concrete implementation of IAmAMessageConsumer which abstracts the Message-Oriented-Middleware used by that Input Channel for the task queue implementation.

Install the Paramore.Brighter.MessagingGateway.RMQ package from NuGet

PM> Install-Package Paramore.Brighter.MessagingGateway.RMQ

This will install RabbitMQ.Client as a dependency.

This will install the Paramore.Brighter.MessagingGateway.RMQ package which provides support for a Task Queue implemented in Rabbit MQ

Step Three

Please see Topshelf's own documentation for how to use it in more depth.

Step Four

We use TinyIoC as a DI container within Greetings, so we need to add that package into the solution as well. Brighter is a DI Friendly Frameworks so you can use the DI container of your choice with Brighter.

PM> Install-Package TinyIoC

Step Five

Brighter uses LibLog to abstract the implementation details of a client's logger. Greetings uses log4net as the concrete logger so we need to add a NuGet reference to that project too.

PM> Install-Package log4net

Step Six

We use boiler plate code to implement the Main method to configure the Topshelf service.

public static void Main()
{
/*
* Send a message in this format to this service and it will print it out
* We document this here so that you can simply paste this into the RMQ web portal
* to see commands flowing through the system.
* {"Greeting":"hello world","Id":"0a81cbbc-5f82-4912-99ee-19f0b7ee4bc8"}
*/

A summary of this code is: it provides callbacks for Topshelf to call in response to OS instructions to a Windows Service to start, stop or shutdown. In other words it configures how we respond to service lifetime events. We use a class called GreetingService to implement our response.

Step Seven

We now need to implement the GreetingsService to respond to the control messages. Add a new class to the project called GreetingService and enter the following code:

using System;
using Paramore.Brighter;
using Paramore.Brighter.MessagingGateway.RMQ;
using Paramore.Brighter.MessagingGateway.RMQ.MessagingGatewayConfiguration;
using Paramore.Brighter.ServiceActivator;
using Polly;
using TinyIoC;
using Topshelf;

The key behavior of Greeting is to configure the Command Processor and the Dispatcher. We covered the basics of the CommandProcessor in the Hello World Example.

We use a DispatchBuilder to build a Dispatcher, which dispatches messages from a Task Queue to a Command Handler. The principle is that once configured you can send messages to handlers in the service without having to write the infrastructure code around reading from a queue, translating the message body into an IRequest (Command or Event), and dispatching to a handler. The goal here is that the task queue should remain transparent to the developer, who simply uses IAmACommandProcessor.Post to send a message from one process and then uses the Dispatcher to read that same message and pass to a handler in another.

We create a Command Processor as part of creating our Dispatcher to map de-serialized Commands or Events to handlers. Note that it may seem counter-intuitive that we set no Task Queue on the Command Processor. This is because we are not sending to a task queue from this service, just reading, so we do not need to configure Command Processor for sending only receiving. The Tasks Example shows an application that has both sending and receiving components.

We add both a Retry Policy and a Circuit Breaker Policy using the Polly library. We create policies to decide what intervals to retry at in the event of failure, and how long to break a circuit for in the presence of persistent failure. We register these policies in the PolicyRegistry, using the well-known names CommandProcessor.RETRYPOLICY and CommandProcessor.CIRCUITBREAKER. Internally, CommandProcessor uses the policies you register when you call IAmACommandProcessor.Post to push a message onto a Task Queue, but you can re-use them yourself. As discussed above, we are not doing a Post here.

We register implementations of IAmAMessageMapper with the MessageMapperRegistry to map the message body from the Task Queue into Commands and Events. In this case we only have one: GreetingEventMessageMapper which we use to map a GreetingEvent to and from the message body (as JSON).

In order to read messages from a Task Queue we need a IAmAMessageConsumerFactory. In this case we are reading from a RabbitMQ Task Queue so we use RmqMessageConsumerFactory. We set this as the parameter to an InputChannelFactory and pass to the DispatchBuilder

The Input Channel is an abstraction over the stream from which we read messages - mostly implemented using Message-Oriented Middleware - and Dispatcher uses the InputChannelFactory to create instances of the stream to read from, as specified in configuration. We pass the application protocol specific factory to this, so that we can create input channels for that protocol. The use of abstraction is intended to allow support for different protocols and implementations of those protocols to be used as the stream that underlies the Task Queue

As outlined in Hello World our goal is to be a DI Friendly Frameworks so we rely on the client implementing a factory to provide instances of handlers and message mappers to us. In this example we use TinyIoC as our DI framework and implement the required factories using that DI framework.

A message has a header - where we write metadata about the message - and a body - where we write the contents of the message.

When mapping to a message, on the header, we set the Message Type to MT_EVENT because we want to allow any number of handlers to handle the message. The topic is used for routing. Subscribers to the message use the topic to indicate their interest in receiving the message

The body of the message is a JSON string representing the GreetingEvent

Because we don't send from this service, we don't need MapToMessage and could simply throw a NotImplemented exception instead.

When mapping back to a request we simply serialize the entity body into the Command/Event we want to raise.

Step Eleven

Now we need to add the handler, which actually does the work. Add a new class GreetingEventHandler to the project

using System;
using Greetings.Ports.Commands;
using Paramore.Brighter;

Add the rmwMessagingGateway section and the serviceActivatorConnections, which configures both the AMQP URI for your RabbitMQ server (amend if you are not using defaults) and the channel over which you subscribe to messages