Building Simple Go Applications with RabbitMQ Communication

  ·   4 min read

Introduction

In the world of distributed systems, inter-process communication is vital for the functionality and reliability of applications. One effective way to achieve this is through message brokers. RabbitMQ, an open-source message broker that implements the Advanced Message Queuing Protocol (AMQP), is a popular choice for building scalable and decoupled systems. This article will walk you through building simple applications in Go that communicate with each other via RabbitMQ.

Prerequisites

Before we dive into the code, make sure you have the following:

  1. Go (Golang): Make sure Go is installed on your machine. If not, download it from the official site.

  2. RabbitMQ: You can either install RabbitMQ locally or use Docker to get it up and running. If using Docker, you can run the following command:

    docker run -d --hostname my-rabbit --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
    
  3. Go RabbitMQ Client: To connect our Go applications to RabbitMQ, we will use the github.com/streadway/amqp package.

    To get started, initialize your Go module:

    go mod init rabbitmq-demo
    go get github.com/streadway/amqp
    

Application Overview

We’ll create two simple Go applications: a Sender and a Receiver. The Sender will send messages to a RabbitMQ queue, and the Receiver will consume those messages.

Sender Application

Create a file named sender.go:

package main

import (
    "log"
    "time"

    "github.com/streadway/amqp"
)

func main() {
    conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
    if err != nil {
        log.Fatalf("Failed to connect to RabbitMQ: %s", err)
    }
    defer conn.Close()

    ch, err := conn.Channel()
    if err != nil {
        log.Fatalf("Failed to open a channel: %s", err)
    }
    defer ch.Close()

    queue, err := ch.QueueDeclare(
        "hello", // name
        false,   // durable
        false,   // delete when unused
        false,   // exclusive
        false,   // no-wait
        nil,     // arguments
    )
    if err != nil {
        log.Fatalf("Failed to declare a queue: %s", err)
    }

    for i := 0; i < 10; i++ {
        body := "Hello RabbitMQ!" + string(i)
        err = ch.Publish(
            "",          // exchange
            queue.Name, // routing key
            false,      // mandatory
            false,      // immediate
            amqp.Publishing{
                ContentType: "text/plain",
                Body:        []byte(body),
            })
        if err != nil {
            log.Fatalf("Failed to publish a message: %s", err)
        }
        log.Printf("Sent: %s", body)
        time.Sleep(1 * time.Second) // sleep for a second
    }
}

In this code, we’re establishing a connection to RabbitMQ, declaring a queue, and sending ten messages to that queue with a 1-second interval.

Receiver Application

Create another file named receiver.go:

package main

import (
    "log"

    "github.com/streadway/amqp"
)

func main() {
    conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
    if err != nil {
        log.Fatalf("Failed to connect to RabbitMQ: %s", err)
    }
    defer conn.Close()

    ch, err := conn.Channel()
    if err != nil {
        log.Fatalf("Failed to open a channel: %s", err)
    }
    defer ch.Close()

    queue, err := ch.QueueDeclare(
        "hello", // name
        false,   // durable
        false,   // delete when unused
        false,   // exclusive
        false,   // no-wait
        nil,     // arguments
    )
    if err != nil {
        log.Fatalf("Failed to declare a queue: %s", err)
    }

    msgs, err := ch.Consume(
        queue.Name, // queue
        "",         // consumer
        true,       // auto-ack
        false,      // exclusive
        false,      // no-local
        false,      // no-wait
        nil,        // args
    )
    if err != nil {
        log.Fatalf("Failed to register a consumer: %s", err)
    }

    go func() {
        for d := range msgs {
            log.Printf("Received a message: %s", d.Body)
        }
    }()

    log.Printf("Waiting for messages. To exit press CTRL+C")
    select {} // block forever
}

In this application, we connect to RabbitMQ and set up a consumer that listens for messages on the same queue. It will print out any message it receives.

Running the Applications

Now, let’s run both applications:

  1. Start the RabbitMQ server if you haven’t already.

  2. Open one terminal and run the Receiver:

    go run receiver.go
    
  3. Open another terminal and run the Sender:

    go run sender.go
    

Expected Output

The Receiver will output the messages sent by the Sender, each prefixed by “Received a message”.

Conclusion

You have successfully created two simple Go applications that communicate using RabbitMQ. This demonstrates a fundamental aspect of distributed applications, allowing for decoupling of services by using message queues.

Feel free to expand on this example by adding more features, such as message acknowledgment, error handling, or scaling up with more receiver instances.

Further Reading and Resources

By exploring these resources, you will gain deeper insights into developing scalable applications using Go and RabbitMQ.