Sending and receiving messages in Go| Beginner’s Guide to RabbitMQ: Part 2

Code Voyager
6 min readOct 21, 2023

--

Explore the world of message queuing as we dive into creating a simple Go producer and consumer to exchange messages using RabbitMQ. This hands-on guide will illustrate the step-by-step process, making message delivery a breeze.

In the previous part of our series, we set up a local RabbitMQ server with Docker Compose, which is the stepping stone for today’s exploration. If you missed it, catch up by reading Jumpstart Your Messaging Journey: Setting Up RabbitMQ Locally with Docker Compose, before moving on.

Now with our RabbitMQ server ready and Docker Compose at hand, we are all set to delve into the practical aspect of message queuing.

Preparation

Ensure you have the following prerequisites sorted:

  • Go installed on your machine. If not, download it here.
  • Your RabbitMQ server from the previous adventure, ready for action.
  • Docker Compose to orchestrate our operations.
  • A code editor of your choice for scripting the action.

Setting the Stage

Navigating to the Project Directory 📁

Let’s return to the rabbit-adventures directory we have created in the previous tutorial:

cd rabbit-adventures

Setting up the Producer and Consumer Directories 📁

For Unix:

mkdir producer consumer

For Windows:

md producer 
md consumer

Defining Producer and Consumer

Setting up the Producer 📜

Navigate to the producer directory:

cd producer

Initialize a new Go module:

go mod init github.com/username/rabbit-adventures/producer

Create a new file named producer.go with the following code:

package main

import (
"context"
"log"

amqp "github.com/rabbitmq/amqp091-go" // Importing the RabbitMQ client library
)

// failOnError is a helper function to output any errors and stop the program
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}

func main() {
// Dial opens a connection to the RabbitMQ server.
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()

// Channel opens a channel to communicate with the RabbitMQ server.
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()

// QueueDeclare declares a queue to hold and deliver messages.
q, err := ch.QueueDeclare(
"go-queue", // name of the queue
false,
false,
false,
false,
nil,
)
failOnError(err, "Failed to declare a queue")

// Message content
body := "Hello World!"

// Publish sends a message to the RabbitMQ server.
err = ch.PublishWithContext(
context.Background(),
"",
q.Name, // name of the queue again
false,
false,
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")

// Log the sent message
log.Printf(" [x] Sent %s", body)
}

Don’t worry if you do not understand all of the different details, we will learn all about them as we progress.

Tidy up the module Go dependencies:

go mod tidy

Return to the project root directory:

cd ..

Setting up the Consumer📜

Navigate to the consumer directory:

cd consumer

Initialize a new Go module:

go mod init github.com/username/rabbit-adventures/consumer

Create a new file named consumer.go and input the code for the message producer:

package main

import (
amqp "github.com/rabbitmq/amqp091-go" // Importing the RabbitMQ client library
"log"
)

// failOnError logs any error and exits the program
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}

func main() {
// Connecting to the RabbitMQ server
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()

// Opening a channel to communicate with the RabbitMQ server
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()

// Declaring a queue to hold and deliver messages
q, err := ch.QueueDeclare(
"go-queue", // name of the queue
false,
false,
false,
false,
nil,
)
failOnError(err, "Failed to declare a queue")

// Registering a consumer to receive messages delivered to the named queue
msgs, err := ch.Consume(
q.Name, // name of the queue again
"",
true,
false,
false,
false,
nil,
)
failOnError(err, "Failed to register a consumer")

// Creating a channel to keep the main function waiting
forever := make(chan bool)

// Processing the received messages in a new goroutine
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
}
}()

// Logging readiness and waiting indefinitely
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}

Tidy up the module Go dependencies:

go mod tidy

Return to the project root directory:

cd ..

Launching the Ensemble 🚀

With our Go scripts primed, it’s time to set the stage for our messaging ensemble. We’ll not only observe the interaction between the producer and consumer through the terminal but also get a visual understanding of message queuing through the RabbitMQ Management Interface.

1. Setting Up:

  • Ensure your RabbitMQ server is up and running with the command:
docker-compose up -d

2. Launching the Producer:

  • Open a terminal window.
  • Navigate to the producer directory and run the producer script:
cd producer
go run .

3. Inspecting the Queue — Pre Consumption:

  • Navigate to http://localhost:15672 and log in using the default credentials: username guest and password guest.
  • Click on the “Queues and Streams” tab
  • And click on the go-queue in the queue list to view it.
  • Scroll down an open the “Get Messages” section and click on the “Get Messages” button to view the message(s) sent by the producer awaiting consumption.

4. Launching the Consumer:

  • Open a new terminal window.
  • Navigate to the consumer directory and run the consumer script:
cd consumer
go run .
  • In the terminal, observe the messages being sent from the producer to the consumer, illustrating RabbitMQ’s message delivery capabilities.

5. Inspecting the Queue — Post Consumption:

  • Return to the RabbitMQ Management Interface and refresh the go-queue page.
  • Scroll down an open the “Get Messages” section and click on the “Get Messages” button to view the message(s).
  • The message count will have decreased as the consumer processes the messages.

6. Exploring Further:

  • Dive deeper by clicking on the go-queue to check out details like message rates and other metrics.
  • Also, venture into the “Exchanges”, “Bindings”, and “Channels” tabs for a broader understanding of RabbitMQ’s operations.

7. Wrapping Up:

  • Once done, remember to log out from the RabbitMQ Management Interface to uphold security.

By utilizing both the terminal and RabbitMQ Management Interface, you’ll gain a comprehensive view of message queuing, delivery, and consumption in action. This practical approach will significantly aid in understanding the core functionalities of RabbitMQ as we advance through this series.

Conclusion

Through a blend of Go, Docker Compose, and RabbitMQ, we’ve demonstrated a simple messaging scenario.

This article builds upon our initial setup, advancing our understanding of RabbitMQ’s role in facilitating communication between different components of an application.

As we continue this series, we’ll explore more features and capabilities of RabbitMQ, empowering you to implement robust messaging solutions in your projects.

Additional Resources

--

--