Learn RabbitMQ for Event-Driven Architecture (EDA)
A beginner-friendly tutorial on how RabbitMQ works and how to use RabbitMQ in Go using an Event Driven Architecture
by Percy Bolmér, March 24, 2023
Event-Driven architecture (EDA) is one of my favorite things in programming. This architecture allows us to build microservices and easily share information between them.
In regular sequential software, you would have a function trigger another function or a recurring script that checks for certain things to do.
With Event-Driven architecture, we instead leverage a Queue or a Pub/Sub schema. Allowing different services to notify or send information between them to trigger code to execute.
Event-Driven Architecture is often used to build software that is highly flexible and extensible. This is a result of being able to easily add functionality or remove by simply listening to events with a new service.
It also makes it very easy to shadow deploy and test new services in parallel with production, since you can make the new service react to the same events without interfering with the running system.
It is not all sunshine and roses however, some consider EDA systems to be a little bit more complex, and sometimes harder to test when considering the full flow of services. I’d say the testing is easier since we can easily trigger an event and see how the related services react, or a single service. But without proper documentation of the architecture, it can also be hard to understand what triggers what and why.
This tutorial will look at how you can use RabbitMQ to build two microservices that communicate using events. We will look at different paradigms used in RabbitMQ, and while we will learn how to use RabbitMQ in Go, we will mostly focus on learning the concepts of RabbitMQ. Covering some common mistakes and a few best practices to follow.
RabbitMQ supports multiple protocols to send data, but in this tutorial, we will focus on using AMQP.
Over the course of this tutorial, we will learn the following
- Setup RabbitMQ using Docker
- Virtual Hosts, users, and permissions
- Managing RabbitmQ using CLI with rabbitmqctl and rabbitmqadmin
- Learn about Producers, Consumers and how to write them.
- Learn about Queues, Exchanges, and Binding
- Using Work Queues (first in and first out)
- Using Pub/Sub with RabbitMQ
- Using RPC-based patterns and callbacks.
- Encrypting traffic with TLS
- Using Configurations to declare resources in RabbitMQ
All code used in the article can be found on Github
Getting RabbitMQ up and running can be done by following the examples at Downloading And Installing. I’d recommend following that guide when going into production, but for this tutorial and to experiment we can use an easier approach.
As always, the easiest way is to run a Docker!
This command will download the latest RabbitMQ and start it as a background process, exposing ports 5672 and 15672.
Port 5672 is to enable AMQP connections. AMQP is the networking protocol used by RabbitMQ and many other Message brokers.
Port 15672 is opened because the Admin UI And management UI are hosted on that port, also the API to manage RabbitMQ is on that port.
For more details on the ports, you can refer to the networking guide of RabbitMQ.
Once the docker is started, let’s begin by visiting the Management UI hosted at localhost:15672.
Whops, we need a User! Let’s create one using RabbitMQCLI. Don’t worry about installing it, it is present in the Docker Container we ran.
We can create a new user with the command
add_user followed by the username and password. We use
docker exec rabbitmq to execute the command inside the docker, replace
rabbitmq with the name you have given the docker container.
I recommend giving Admin access during the exploration as well. We can do this by adding an administrator tag to the new user.
Oh, one last thing, there is a
guest user by default, I strongly recommend removing this user! This user is only available to users using localhost, but still better safe than sorry.
That’s it, back to the Management UI and log in.
Once logged in you will see a rather old-looking UI, but it is great because we can really monitor RabbitMQ from here, and see what is going on. We will not play around with the UI yet, we need a service that actually connects and sends data first.
Before we start hacking, we need to fix two more things.
Resources in RabbitMQ, such as Queues and other things we will learn more about soon are separated by a logical layer, this logical layer is called a Virtual Host (Vhost).
The easiest way to explain Virtual Hosts would be, but probably wrong in some ways, to compare it to a Namespace.
We can use these vhosts to group certain resources together and restrict access by adding the users who are allowed to use the vhost.
Let’s begin by creating the vhost using the
add_vhost command, it accepts a single input which is the name of the vhost.
Now that we have a vhost, we can add permissions to the user we created earlier so that it is allowed to connect.
Adding permissions is done with
set_permissions command, we use the
-p flag to specify the vhost to add permissions on. The next item in the syntax is the user to add permissions.
The last part of the command is the scary part, it’s a regexp that defines what permission to add, an example to add all permissions could look like, or permissions on all resources beginning with
customer- would be
There will be 3 regexp slots, in order, it configures the permissions for the following.
- Conf - Permission to configure resources matching regexp
- Write - Permission to write on resources matching regexp
- Read - Permission to read on resources matching regexp
The full command to add full access to configure, write, and read on the customer vhost for my user
percy would look like this. Note that I give access to
.* which is everything.
After creating this, you should see the new vhost in the top right corner of the management UI.
- Producer - Any software that is Sending Messages.
- Consumer - Any software that is Receiving Messages.
- Queue - An Queue accepts a Message, outputs the message, think of it like a large Buffer. Queues are FIFO (First in First Out) meaning that messages are outputted in the order they are inserted into the queue.
- Exchange - An Router, is the place where Producers send messages. The exchange accepts messages and sends them to the correct Queues depending on the type of Exchange and the Bindings (Rules) Applied.
The general idea is that we can use this to send and receive messages between Services. It is worth mentioning the Producers and the Consumers do not have to be on running on the same Host, allowing this to scale very well.
Begin by creating a new project with Go, If you don’t have Go Installed, please install it from here.
In a real go Project setup I would probably use Cobra, but to avoid new users from being confused I will simply create two main packages.
Let’s build a Producer in Go that can start sending messages on a Queue.
Begin by creating a new project for the producer and get the AMQP library that is officially maintained by the RabbitMQ team.
The project will have a
cmd folder that will hold all the different services, each being it’s own runnable.
We will also have a
internal folder that is used to store shared libraries etc.
Your folder structure should look like the following.
Let’s add a connection to the RabbitMQ instance first inside
We will create a small helper function that will connect to RabbitMQ using the
amqp protocol. We will allow users to specify the credentials and host, also the vhost to connect to.
I will simply return pointers to the connection, which is the network connection, and
amqp.Channel which is used to concurrently send messages. Leaving it up to the users to manage the connection.
A really good rule of thumb is to reuse a single Connection across the whole application and spawn new Channels for concurrent tasks. The reason for this is that a Connection is a TCP connection, while a channel is a multiplexed connection in the assigned TCP Connection. Following that rule of thumb allows for a much more scalable solution.
Let’s import this simple client into the cmd/producer/main.go and try connecting to see what happens.
For now, we will simply connect and sleep for 30 seconds before closing the connection.
Once we have that in place, run the producer.
Once that is running, navigate back to the management UI and see that we can actually see there is now 1 Connection and 1 Channel.
Channels are a really smart way of handling the TCP layer, you can read more about it in the docs. It allows users to reuse an open TCP connection with multiple Channels, instead of opening many TCP connections. It’s a multiplexing technique.
It’s time to start sending data, this is done on said Channel. There is more to a channel than one can probably think, it’s not just a dumb pipeline, there are actually some nifty options that can be configured when creating them.
We can create Queues from the UI, but I like creating them in the Code when Testing things out. In a production environment, I like having a configuration file to declare some basic setup, we will look into that later.
We can create a Queue by Calling the
amqp.QueueDeclare, this function has many input parameters that we need to understand to get the wanted behavior of the Queue. The signature looks like this.
- Name- The name used to reference the Queue. This can actually be empty, in that case, the server will generate a name.
- Durable- If the Queue should be persisted across Broker restarts (RabbitMQ restart)
- AutoDelete- If the Queue should be automatically deleted when the last Consumer leaves
- Exclusive- Only available for the same Connection that created the Queue.
- NoWait- Assumes the Queue is created on the Server
- Arguments- An option to provide user-provided arguments.
To make this a little easier, I will create a wrapper function that accepts the
autodelete parameters. I will leave the others disabled by default.
Let’s update the
producer/main.go to execute the new CreateQueue function, I will create a durable queue since I want the Queue handling new customers to be kept alive and persistent, I will also make auto delete set to
I will also create a nondurable Queue named
customers_test to showcase the difference.
After adding that, make sure to execute the producer.
You can visit the UI and view the Queues that should be both available. Notice that once the program exists, the
customers_test queue is NOT DELETED, this is because we have yet to have a Consumer connect. Only Queues that had a consumer connect will be deleted.
For fun, you could try to restart RabbitMQ now and see how
customers_test is gone, since it was not marked as durable.
Before we can start sending messages on the Queues, we need to create an Exchange. There are a few defaults already created, but we will create our own to learn a little bit more about them.
The Exchange is a vital part of RabbitMQ, it is the resource to which we send Messages. The exchange’s job is to deliver the messages to the correct Queues.
To start receiving messages on a Queue, that queue needs to be bound to an Exchange, this is called Binding. Binding is basically a routing rule. One important thing to understand is that a Queue can be bound to MULTIPLE Exchanges, which makes it much more clear why there are different Exchange types also.
There are a few different types of exchanges, each with different behavior of how messages are sent.
First, we have the most basic one, the Direct exchange. This one is really simple, messages are routed based on their EXACT routing key. In the example image, we see how the message sent to
customer_created is only routed by the exchange
customer_events into that specific queue. Direct exchanges are very useful when you need to distribute work to a set of workers.
The second type is the Fanout which is used to send messages to ALL bound Queues. Any Queue that is bound to the exchange will get the message, the routing key is simply ignored! This is typically used for broadcasting messages to any parties interested.
Then we have Topic Exchanges, which once are pretty cool. They allow bindings to specify a rule to select subsets of the messages sent depending on their routing key.
The routing keys are delimited
. between each word, say
customers.eu.stockholm. This might be the routing key for customers from Stockholm Sweden, then we could have a binding that tells the Exchange that a Queue wants those messages, but not
customers.us.florida for example.
There are a few special characters, the
# means zero or more matches, so for example
customers.# would match any routing key beginning with
There is also
* which is a specific word at a specific location, for example
customers.*.stockholm would only match routing keys that have the first word
customers and the third word
This is of course very useful to have certain services only ingest messages that are related to a subset of the topics. The example below shows how a new customer is created in February, the Queue
customer_created gets the message since the binding is for the
customers.created.# rule, the Queue
customer_emailed does not receive it since it does not match the binding
The final exchange is the Header exchange, each Message we send on RabbitMQ has the possibility to add Headers, which is a key value field. This can be really handy when we need to route based on a more advanced level.
Say that we add a
browser header which indicates what web browser the users used when registering. We could route any Linux users to a certain queue for instance.
You can either specify multiple headers and that they all have to match, or only that one has to match. This is done in the binding by setting
x-match to either
Let’s stop talking and instead create an Exchange that we can use.
To add an exchange, we will be using the
rabbitmqadmin CLI tool that is very similar to
rabbitmqctl that we used before.
We use the
declare exchange command followed by the name of the exchange, and the type. For this tutorial, I will be using an
We will create an exchange named
customer-events. We also need to specify the vhost and the username and password of our administrator. Remember to set durable to true if you want it to persist restarts.
We also need to give the user permission to send on this exchange. We set permissions on a certain topic using the
set_topic_permissions command. The following command sets the user
percy to allow posting on the vhost customers on the exchange
customer_events on any routing key starting with
Posting on this Exchange right now will not make anything happen, because we have no Bindings between the Queues ad the Exchange.
Any messages sent will be discarded.
To start publishing messages, we first need to create the binding between
customers_test queues and the
rabbitmq.go and add a
CreateBinding function that adds the binding.
producer/main.go we add bindings so that we connect everything. We will expect customers to be published on the topic
customers.created followed by the country they are from. But the Binding won’t care about the country, only that it matches the pattern.
If you execute the producer once we can visit the management UI and see the bindings that are available.
Then go into the UI and visit your Exchange.
Now that we have a binding, we can look at publishing messages. We begin with the easiest kind.
We create a wrapper function called
Send that accepts parameters about what exchange, and routing key to publish to. The function will also accept a context to use and a
amqp.Publishing struct is essential to understand, as it allows us to customize the features and behavior of the message we send.
We will explore them step by step since there are quite a few of them.
Go back to
producer/main.go and we will create a message to send. We will send two messages, one for each queue. This is to showcase the
deliveryMode parameter which is really important. If you set it to persistent the message will be saved until some consumer grabs it, but this comes with overhead and longer latency.
If you have things that do not need to be durable, then set them to Transient to increase performance.
Remember, if you send persistent messages, your Queue also needs to be Durable, no point in saving Messages across Restarts, if the Queue itself is gone.
Time to execute the Producer again.
You should now see one message for each Queue in the UI under the Queue page.
If you want to, you can enter each queue and Consume the messages to view them, but I suggest a restart of RabbitMQ to show the difference between Transient and Persistent.
After restarting try reloading the UI, you should see that the whole
customers-test queue is removed, but the
customers-created queue actually has the old message left.
This is because Persistent messages are written to disk to survive crashes etc.
We will cover more advanced publishing soo.
We know how to publish messages, but that does us no good if we cannot Consume the messages in another service.
Consuming is the process of grabbing a message from a Queue.
Let’s create a new binary that we can use to consume messages.
Before we start consuming, we will add a
Consume function in
Rabbitmq.go that will wrap the channel consume function.
When consuming there are a few options to consider.
- Exclusive - If set to true will ensure that this is a SINGLE and the ONLY consumer on that Queue, if False the server will fairly distribute messages amongst consumers.
- AutoAck - When true, will automatically acknowledge delivery, when false will expect the Consumer to call Acknowledge when complete. AutoAck might sound amazing, but it’s tricky, if your consumer fails after Acking a time-consuming process, the message is lost because the server thinks it’s done.
- NoLocal - Not supported in RabbitMQ, AMQP field for avoiding publishing and consuming from the same domain.
- NoWait - Won’t wait for the server to confirm.
Let us add the Consume function to Rabbitmq.go
Now that we can consume, let’s fill in the
consumer/main.go so that it connects to RabbitMQ and starts Ingesting messages from the queue.
Running that consumer should print out a Message coming from the publisher once it happens.
Remember, Reuse the Connection, but create a new Channel for each Parallel processing, in our case a second RabbitMQ Client would be created to manage the
If you don’t see any messages it is probably due to the fact that you need to run the producer first.
It might be worth exploring the Struct that is sent over the channel, the
amqp.Delivery struct, it gives a good idea of all the fields present.
If you rerun the current consumer, you will see the same message appearing again. This is due to the fact that we never acknowledge that the consumer used the message. This has to be done manually when iterating the messages or using the Auto Acknowledge flag.
When acknowledging we can pass in a
multiple flag, which indicates if we acknowledge multiple messages at once, we can leave it to be false.
We can acknowledge or NACK messages, Acknowledge means that everything was fine, Nack means we failed to process it and then the message will be passed back into the queue.
Let’s update the code that consumes messages so that It acknowledges them.
Rerun the code now, and you should see that the message is printed once again, but upon restarting the message is gone.
This is really useful, to avoid having a consumer take a message, fail while processing it, and then that message would be gone.
To showcase why Auto Ack might be dangerous, here is a modified example where we set Auto Ack to true, but failed during the processing.
Run the consumer twice, and you will see that it is in fact only accepted at the first execution. This can be dangerous behavior if you don’t manage it properly. That’s why I keep mentioning it!
To handle failures, you can use
Nack to tell RabbitMQ that it failed, and you can use the
redelivered field to avoid retrying too many times.
Nack accepts a parameter for Requeuing, which is really handy!
Here is an example where we fail the first time a message arrives, Requeue it, and then Ack it the next time it comes.
There is more to consider here, right now the handler we use is single-threaded, which means we can only accept one message at a time. We can fix this by implementing a work group that allows for a certain amount of concurrent tasks.
I will add a
errgroup, so this approach requires Go 1.2. Using an ErrGroup is super easy, and we can limit it to 10 messages per Consumer.
The errgroup is from the
Adding this makes the consumer a little bit better.
The SetLimit is only used for now, there is another way to manage how many messages are consumed, using RabbitMQ which I Recommend called Prefetch, we cover that later
We can update the Publisher to spam a few more messages by wrapping the Send function in a for loop.
Try it out and see that the Consumer now accepts multiple messages, or even try spawning multiple consumers to play some.
Noticed that the Producer exits right away after sending the message? Currently, the
Send function does not wait for any confirmation from the server. Sometimes, we might want to block until the server confirms that it has received the message.
Happily, we can! We need to change the
Publish function we use in RabbitMQ into
PublishWithDeferredConfirmWithContext which will return an object that we can use to
Wait for the server to acknowledge.
This object will always be NIL unless the Channel is put into
Confirm the mode, setting it into Confirm mode will make the server send an acknowledgment when it receives the published message.
Rabbitmq.go let’s change the Publish method and add a Wait.
Let’s also update the
NewRabbitMQClient to always set the channel into
A better approach to
Rabbitmq.go could be to add a
NewChannel function and then have each function accept a Channel as an input parameter.
Running the program now, and you should see that the
publisher.go prints TRUE each time a message is confirmed by the server, notice that this is not the same as the Consumer
ACK. We only wait for the server to acknowledge that the published message is accepted.
Up until this point we have been using FIFO Queues(First in First out). This means each message is only sent to one Consumer.
In a publish and subscribe schema, you will want each consumer to receive the same message.
Everything we learned about bindings etc is still true and used in the same way. We can use a Fanout exchange (which pushes messages to all Queues bound) regardless of the Queue name.
The idea is that you let each Consumer create an Unnamed Queue, an unnamed queue will get a randomly generated unique name by the RabbitMQ server.
This is a great example when creating queues in the code is suitable
It’s possible that we would want to send
customers_event to multiple services. Say that we would want an email service and a logging service that logs each customer event.
Let’s build that. (Since this is a tutorial only to learn RabbitMQ we will simply start the Consumer up in two instances).
We start by removing the exchange we have since it is the wrong Type. We also create a new one, but with
Fanout as the type. This time we don’t specify a certain prefix for the permission, but give it full access.
Since we won’t know the Queue name when we create an unnamed one with the current code, we need to modify it. Let’s return the Queue information returned from
CreateQueue in the RabbitMQ package. That object will contain the randomly created name.
Time to update the
Publisher, earlier in the tutorial we created the Channel bindings in the Publisher. This doesn’t really make sense according to me, it was only for the purpose of not moving too fast and still showcasing the functionality.
It makes much more sense for the
Consumer to declare the bindings, since it relates to the consumer. It makes even less sense now with Publish And Subscribes where the number and paths of Consumers are probably unknown. Let’s update
publisher.go making it much smaller.
We will update the
consumer.go to create a Queue without a name, create the binding, and then start consuming that Queue.
This setup can be used to showcase Pub/Sub properly, we can spawn two consumers and then the Publisher. It will show how all Consumers see all the messages.
We now know how to use regular Queues and PubSub.
There is one more thing left, a third very common scenario is an RPC-based paradigm.
Sometimes, we want to have some callbacks on messages. Say that the Producer would like to know when the customer has emailed.
This is common and easily fixed. We can set a field named
ReplyTo in the messages, this can be used to tell the Consumer to reply with a response on a certain Queue.
We might need to know which message the callback is related to, so we can also add in a
correlationID which can be used to understand what request the response is related to.
Begin by creating a new Exchange with Direct type. I’ll name it
customer_callbacks. The Direct type works well here.
The first thing we need to understand is one important best practice at this time.
Having a callback will require that the same service both Publishes and Consumes messages, and it is nothing wrong with that.
One famous rule is, however, to reuse connections for multiple channels. BUT NEVER PUBLISH AND CONSUME ON THE SAME CONNECTION.
Imagine if you have an service that is both producing and consuming and you are doing it on the same connection, then imagine that the service is consuming a big amount of messages. If it is more messages than the service manages to handle, so messages starts stacking up. RabbitMQ May then apply back pressure and start blocking TCP connections from sending, and guess what, ACK messages have to be sent to handle messages. All of a sudden, your code cannot ACK messages since the connection is blocked. This can lead to delays.
The golden rules are
- Reuse connection in your application
- One Connection for Consuming, One for Publishing
- Spawn new Channels for each Goroutine
Let’s update the
producer.go to start two connections, one for publishing and one for consuming. We will also create an unnamed queue and bind it to the exchange, then we will consume those responses.
We will also add
replyTo in the message, which tells the consumer where to reply, and the
correlationId which explains which unique event the messages relate to.
The consumer needs to be updated so that it also uses two connections. When we are done processing a message, we will add it so that we send out a response on the
replyTo queue. Again, we have to use two different connections, one for consuming and one for publishing.
Go ahead and try the code, you should see that the producer receives the RPC responses and prints them out.
Note that this code can be cleaned up, but this tutorial focuses on how RabbitMQ works, not clean code.
Remember that we limited the amount of work done by the consumer before by using an
errgroup? This was a soft limit, a limit imposed by the code, but RabbitMQ can still send more messages out to the consumer.
There is a better fix to this, well, a combination should probably be used if you want your consumer to handle messages concurrently.
The AMQP protocol allows us to apply a Prefetch limit. This tells the RabbitMQ server how many unacknowledged messages can be sent to the channel at a time. This way we can add a hard limit.
This is done by applying a set of Quality Of Service rules (QOS). Let’s add a method in
rabbitmq.go that applies the three available rules.
There are the following parameters
- Prefetch Count - How many unacknowledged messages the server can send.
- Prefetch Size - How many bytes of unacknowledged messages the server can send.
- Global - A flag that determines if the rule should be applied on the connection, or globally
consumer.go we can simply call this and apply the number of messages we want to allow.
It’s the year 2023, before going into production I think it is very safe to say that we should encrypt the traffic.
RabbitMQ has a GitHub repository to help us create a rootCA and the certificates we need, which is the first step in starting to encrypt the traffic.
We need to clone this repository and execute the make file inside that will generate the files we need.
All the generated files will appear in a new folder called result. For this to work in Docker, we need to change their permissions.
We need to delete the RabbitMQ container that is running, we will need to create a new one with a configuration file.
The configuration file is called
rabbitmq.conf and should be placed inside
/etc/rabbitmq/rabbitmq.conf in the container.
This configuration file can be used to configure much more than just TLS, but we will stick to TLS for now. Create a new file with the correct name in the root of the project.
We need to mount the configuration file into the docker when we start the container. We will also mount the certificates generated from the TLS-Gen tooling into
/certs so that the container can find them. Notice that both ports have been decreased by one, which is the standard in the RabbitMQ protocol.
Once that is done, we can start adding TLS configurations to this container.
rabbitmq.conf let us add the PATH to the certificates and the root ca. My computer is named
blackbox, you need to replace the certs names with the names generated by your computer.
Then restart RabbitMQ
To verify that everything works, you can view the docker logs with
docker logs rabbitmq. Search for the logs about listeners.
Now, the old programs won’t work anymore. It tries to connect using without TLS, so let’s fix that.
The programs need to be updated to use client certificates. Let’s add it as input to the
Please notice that we now use
amqps protocol. The certificate paths are absolute paths, let’s update the
producer to insert these, I will use hard-coded values for now, but you should not do that in a real application.
BAM! Amazing, we got TLS.
Try running either the producer or consumer, and then watch the docker logs using
docker logs rabbitmq.
Right, we removed the Vhost, the user, the exchange, and everything when we deleted the docker since we don’t persist the storage.
This is great because that brings us to the next and final step of this tutorial, default configurations.
Trust me, you don’t want to manage a rabbitMQ for multiple users using the AdminCLI, because, well it is a lot of work repeating stuff if you reset the cluster for some reason.
There is support for inserting definition files, JSON files that define users, vhosts, permissions, queues and exchanges, and even bindings.
They are really easy to use, let’s add my old user and give it permission to write and read on the
customers vhost, and add a basic exchange.
Before that, we need a password hash, which can be a bit trickier than imagined. It depends on the RabbitMQ setup you have and which algorithm you have configured. The default one is SHA256.
I found a great bash script on stackoverflow to generate it for me. Create a file named
encodepassword.sh and replace
secret with the password, you wanna encode.
Run the script
bash encodepassword.sh and store the Hash.
rabbitmq.conf to include the field
load_definitions which can be used to load definition files at start-up.
I will point toward a file named
Create a file named
rabbitmq_definitions.json in the root of the project, and fill it with the following JSON. At this point, I don’t think we need to cover the JSON fields, everything should be understandable and clear what is what. It is very similar to the CLI commands we ran before.
The following definition file creates the two exchanges we have
customer_events and the
customer_callbacks. The current code will generate its own Queues, so we just define an in the example for fun.
Once both files are in place, delete the old docker, and restart a new one, but this time we add a third mount for the definitions.
After running that, verify the logs that they print out creating the user.
Once that’s done, try running the consumer and producer and you should see that everything now works as expected. The only difference is that we now create the infrastructure in RabbitMQ using a configuration instead of using the CLI, and the traffic is encrypted.
Sadly, this is the end of this long, but thrilling and exciting adventure with RabbitMQ.
Let’s take a look at what we learned.
We have learned how to configure RabbitMQ with virtual hosts, and how to create users with permissions on those vhosts. We have also learned how to produce and consume messages on Queues and Exchanges.
You should be familiar with all the resources, such as Queues, Exchanges and Bindings.
We have also covered how to create a Publish and Subscribe schema, a RPC pattern and regular Work queues.
Hopefully, it is clear how to use Connections and Channels and the difference between them. The connection is a TCP connection, and a channel is a multiplexed virtual channel over a connection. Reuse Connections in the same software, but create new channels for each parallel process.
We learned never to produce and consume on the same connections.
And we have covered how to setup TLS and how to add definitions for predefined configurations for RabbitMQ.
I really hope you enjoyed this tutorial, and you can find all the code used on GitHub.
Feel free to reach out to me with any questions!
If you enjoyed my writing, please support future articles by buying me an Coffee