In this post we'll explore our options regarding how different services could communicate with each other. There is no code in this post, we'll just explore some different architectures and the trade-offs included with each one. In the end you can make up your own mind about your own specific domain. I have my preference for Govrnanza and in the next part we'll implement that strategy and get into some code.
In Part 2 we covered the patterns and topologies that RabbitMQ enables. In this part we'll look at Kafka and contrast it against RabbitMQ to get some perspective on their differences. Remember that this comparison is within the context of an event-driven application architecture rather than data processing pipelines, although the line between them can be a bit grey. Perhaps it is more like a continuum and this comparison focuses on the event-driven applications end of that continuum.
In this part we're going to forget about the low level details in the protocols and concentrate on the higher level patterns and message topologies that can be achieved in RabbitMQ. In Part 3 of the series we'll do the same for Apache Kafka.
First we'll cover the building blocks, or routing primitives, of RabbitMQ:
- Exchange types and bindings
- Dead letter exchanges
- Ephemeral exchanges and queues
- Alternate Exchanges
- Priortity Queues
Then we'll combine them all into a set of example patterns.
In this part we'll explore what RabbitMQ and Apache Kafka are and their approach to messaging. Each technology has made very different decisions regarding every aspect of their design, each with strengths and weaknesses. We'll not come to any strong conclusions in this part, instead think of this as a primer on the technologies so we can dive deeper in subsequent parts of the series
Messaging is at the core of many architectures and two giants in the messaging space are RabbitMQ and Apache Kafka. In this series we'll be taking a deep look at RabbitMQ and Kafka within the context of real-time event-driven architectures. We're not talking about data processing and analytics pipelines which is where Kafka clearly shines, but as the messaging platform for business domain events.
Apache Kafka is ascendant right now and RabbitMQ is not talked about as much these days as it was. The hype has been centered on Kafka for good reason but RabbitMQ is still a great choice for messaging. One reason Kafka has stolen the limelight is the industry's obsession with scalability and clearly Kafka is more scalable than RabbitMQ but most of us don't deal with a scale where RabbitMQ has problems. Most of us aren't Google or Facebook. Most of us deal with daily message volumes of hundreds of thousands to hundreds of millions, not billions to trillions. Though I know that people have scaled RabbitMQ to billions of daily messages.
So in this series we're going to largely ignore extreme scalability and concentrate on the killer features that both messaging systems offer. What is so interesting is that they do both have fantastic features but they are so different. I may have written about RabbitMQ a fair amount in the past but I have no special affinity or bias towards it. I appreciate well made technology and both RabbitMQ and Kafka are mature, reliable and yes, scalable, messaging systems.
In this series we'll start at a high level and then start exploring different aspects of the two technologies. This series is for the messaging junkie or architect/engineer that wants to understand the lower level details and their implications. We'll not be writing code in this series, instead we'll focus on the functionality offered by both systems, the messaging patterns each enables and the decisions engineers and architects need to make.
Also check out a couple of webinars on RabbitMQ vs Kafka I did with Erlang Solutions:
Finally, if you really want to see how RabbitMQ and Kafka cope with different failure scenarios then check out:
When a messaging system lies at the heart of your application architecture you need to make it easy to respond to message processing failures. The more queues you have the more important a coherent incident response capability is. Unfortunately, it is all too common to see a chaotic policy or no policy at all for handling messages that cannot be processed successfully. Messages get delayed or lost on a regular basis and no-one is even sure how many.
The normal approach is to create a dead letter queue on a queue by queue basis and send messages there that cannot be processed. But what do you do from there? In this post we'll be looking at a message lifecycle baked into the messaging architecture that can solve this problem.
Problem: You want either a fixed or exponential back-off with your retries. There are a few ways of making this work and all use the dead letter exchange functionality. The best I have seen is NServiceBus and unfortunately there are a few blog posts out there with just plain bad advice. In this post we'll look at the bad advice, some per application solutions and also the NServiceBus method which works as a central retry infrastructure for an entire virtual host.
NServiceBus provides really nice customisation features for your recoverability functionality. We already saw how you can customise the immediate and delayed retries. In this post we'll look at implementing our own custom policy which allows us to make decisions based on the exceptions that are thrown in the message handlers.
The NServiceBus documentation on customising your recoverability policy is well explained and so I recommend you read their documentation first.
Errors can occur while processing messages and NServiceBus has a few different features to help you recover from failures. Retries are an obvious way of recovering from failure and NServiceBus offers two types of retries.
In this post we'll implement a class that inherits from IRoutingTopology and that plugs into NServiceBus to create a custom routing topology. There is also the IDeclareQueues interface that we could also implement but I doubt I would ever need to. NServiceBus creates a bunch of queues in addition to the main endpoint queue, for things like retries and timeouts. I don't want to mess with these as they are important for how NServiceBus gives us the functionality that it does. So in this post we'll just be customising the RabbitMq artefacts beyond the immediate endpoint queues.