Examining how NServiceBus configures and uses RabbitMQ / AMQP
NServiceBus acts a .Net (C#) distributed messaging abstraction layer for message driven systems. It runs on top of a variety of transports including MSMQ, Azure Storage Queues, ActiveMQ on RabbitMQ. This blog describes how NServiceBus configures and uses RabbitMQ using the Video Store Sample program available in the NServiceBus github repository .
Background
Message driven systems are asynchronous distributed systems that have two main message concepts, at least in our project. They have commands, targeted messages, where the sender knows the intended destination. The producer sends the messages to a target. They also have publish/subscribe or event based messages where the sender target is unknown. The sender posts the event where is then received by parties that have registered interest, subscribed.
- Producers: Programs that send messages or post events
- Consumers: Programs that receive messages or capture events
- Queues: A place where messages are collected and distributed. Messages are put in queues where they are read by consumers.
AMQP Background
AMQP is an open standard based messaging system. Some of the fundamental building blocks leveraged by NServiceBus are
- Channels: These are the path that producers communicate over when sending messages
- Queues: Consumers attach to these to read messages. Queues exist to serve consumers
- Exchanges: Aggregation and distribution points used to route incoming messages from the channels to the queues. There can be multiple upstream producers and multiple consumers for the data coming out of an exchange. There are a variety of exchange types discussed in the RabbitMQ documentation. Exchanges can have upstream and downstream exchanges meaning a message can pass through multiple exchanges before getting to a queue.
Useful rabbit tutorials can be found here
NServiceBus Background
NServiceBus uses a type driven model similar to MVC that is familiar to .Net developers. NServiceBus messages are explicitly defined concrete classes or interfaces Each message or event type is a different C# type. It supports message routing and topic subscriptions based on explicit C# types, namespaces or assemblies. Content based routing is not supported. Classes and interfaces can be declared as NServiceBus messages and events with marker interfaces or through a config file. The latter is nice when you don’t want any NServiceBus dependencies in your message model.
- Commands: Point to point messages sent by a client with an expected target. The target is configured in the executable’s properties config file. Commands may or may not expect a reply or an event. The caller must know the target and routing is configured on the sender’s end.
- Messages: Usually these are the types of the asynchronous replies to commands. The system provides auto-correlation support so that the originator receives the reply. These are different from Events where all interested parties can seen message sent as a result of some action. The process replying doesn’t have to know the target. NServiceBus takes care of the routing.
- Events: These are pub/sub messages that are broadcast to interested parties. The message producer has no idea who might be interested in the message. Routing is driven off of the endpoints that are interested in the message.
Message producers know where they want to send commands. Commands, direct messages, are routed to explicit endpoints or queues. Message target queues are defined in an XML file configuration usually web.config or app.config. Here is an example from the NServiceVideoStore example that binds a namespace to an outbound queue name:
<add Assembly="VideoStore.Messages" Namespace="VideoStore.Messages.Events" Endpoint="VideoStore.Sales"/>
class OrderAcceptedHandler : IHandleMessages<OrderAccepted>
public void Handle(OrderAccepted message)
The VideoStore Example
NServiceBus has a Video Store example that has five (5) executables including a web interface, saga processor and hosted NServiceBus endpoints:
- ContentManagement: Simulates a download service
- CustomerRelations:
- ECommerce: A simulated web store
- Operations:
- Sales: A sales management saga
There are three (3) targeted messages, commands:
- ProvisionDownloadRequest: Sent from Content Management to Operations
- SubmitOrder: Sent from ECommerce to Sales
- CancelOrder: sent from ECommerce to sales.
There are five (5) published events that are processed in a publish/subscribe type way.
- ClientBecamePreferred: Published by Customer Relations. Subscribed to by CustomerRelations.
- DownloadIsReady: Published by ContentManagement. Subscribed to by ECommerce
- OrderAccepted: Published by the Sales saga. Subscribed to ContentManagement and CustomerRelations
- OrderCancelled: Published by the Sales saga. Subscribed to by ECommerce
- OrderPlaced: Published by the Sales saga. Subscribed to by ECommerce
Here is the command/message/event configuration from the VideoStore example that tells NServiceBus how to categorize the various message types.
Configure.Instance
.DefiningCommandsAs(t => t.Namespace != null
.DefiningCommandsAs(t => t.Namespace != null
&& t.Namespace.StartsWith("VideoStore")
&& t.Namespace.EndsWith("Commands"))
.DefiningEventsAs(t => t.Namespace != null
.DefiningEventsAs(t => t.Namespace != null
&& t.Namespace.StartsWith("VideoStore")
&& t.Namespace.EndsWith("Events"))
.DefiningMessagesAs(t => t.Namespace != null
.DefiningMessagesAs(t => t.Namespace != null
&& t.Namespace.StartsWith("VideoStore")
&& t.Namespace.EndsWith("RequestResponse"))
.DefiningEncryptedPropertiesAs(p => p.Name.StartsWith("Encrypted"));
.DefiningEncryptedPropertiesAs(p => p.Name.StartsWith("Encrypted"));
NServiceBus on RabbitMQ
NServiceBus endpoints all have a single inbound queue. All messages sent to that endpoint go over that single queue where they are then distributed to the individaul handlers. NServiceBus configures the message and exchange routing so that all Commands and Events destined for an endpoint end up in that endpoint’s single inbound queue. RabbitMQ creates that inbound queue on endpoint startup when NServiceBus binds that endpoint to a queue name in RabbitMQ. Note: AMQP brokers create queues when a receiver binds to queue name, not when a message is sent to a queue name.
NServiceBus makes use of the default message exchange for all Command routing. It explicitly RabbitMQ exchanges for Event pub/sub traffic.
Commands
NServiceBus makes use of the default message exchange for all Command routing. It explicitly RabbitMQ exchanges for Event pub/sub traffic.
Commands
NServiceBus uses the AMQP default exchange that is automatically configured in every RabbitMQ VHost. The VideoStore example has two Command producers with three commands between them. There are two endpoints that receive these commands.
- ContentManagement sends the ProvisionDownloadRequest command to the Operations endpoint through the Operations queue
- ECommerce sends the SubmitOrder and CancelOrder commands to the Sales endpoint through the Sales queue.
Commands are sent with Bus.Send. NServiceBus knows to set the AMQP routing key to the endpoint name before pushing onto the channel and into the default exchange. The default exchange naturally uses the routing to find a queue with the same name.
Events
NServiceBus uses the AMQP exchange-to-exchange and exchange-to-queue routing to build a delivery map for published events. This lets NServiceBus support any many-producer each with many event types with many interested parties.
The NServiceBus publish command matches the C# Type of the event to an exchange name sends the event to that exchange. The exchange points at all the endpoint exchanges that front the individual endpoint queues. This diagram shows how the Video Store’s three (3) event producers post the five (5) different event types to three (3) different consumers.
It’s pretty straightforward once you see the diagram.
- NServiceBus scans for all the Event types based on either the marker interfaces or the Unobtrusive configuration file. It builds a Fanout exchange for each event type.
- NServiceBus scans every endpoint to see what if they support any events. It builds a a Queue Proxy exchange for each identified endpoint with the same name as the queue.
- You can see in the picture above that two of the endpoint queues do not have matching exchanges. This means they don’t subscribe to any events.
- NServiceBus uses the same scan to build a binding between the Event exchanges and the appropriate Queue proxy exchanges.
- The endpoints listen on the queues for messages.
Events are posted using Bus.Publish() . The publish process looks like:
- NServiceBus sends them to the exchange with the same name as the event type.
- The exchange then passes the message to all bound queue (proxy) exchanges .
- The queue proxy exchanges forward all the gathered messages onto their bound queues.
Wrapup
NServiceBus has a type based messaging and routing model that it implements on top of RabbitMQ transport by leveraging the AMQP exchange and queue architecture. It only uses the AMQP fanout exchange. AMQP header and content based routing is not used because it is not needed to support the NServiceBus routing and distribution design.
Joe
Outstanding article. The best I have seen yet.
I'm curious on your diagram.
You have producers to exchanges to queues.
And consumers reading from queues. I'm under the impression in AMPQ that you never directly write to a queue and you never directly read from a queue.
So would the diagram not have a "channel" from which the Consumer C1 opens to read from the Queue?
And would the producer not open an channel to the exchange?
It seems trivial but to me it's what separates AMPQ from MSMQ. The entire concept of "you don't directly write to queues and you don't read from queues". It's why MSMQ is considered a dump transport. And why AMPQ transports can infer Enterprise Patterns like Publish/Subscribe etc..
Love this post!
Eric
This comment has been removed by a blog administrator.
HI Joe
This is a great post.
However the link to the sample source code doe snot work. Do you have it somewhere else as well where i can take a look?