DEV Community

Cover image for How to use ZeroMQ Request-Reply Pattern in Golang
Francisco Mendes
Francisco Mendes

Posted on

How to use ZeroMQ Request-Reply Pattern in Golang

Overview

I bet many of us have thought about decopulating a backend and splitting it into microservices. Let's say you have a monolithic backend and then you decide to add something like a newsletter and you'd rather have a microservice that has the sole function of sending emails.

On the internet you will find several solutions to solve this challenge, but one of the most common is the use of a message broker. However, not all of us need a solution as advanced as the use of a message broker, it is in these specific cases (smaller applications) that I like to use ZeroMQ.

If you don't know ZeroMQ, that's okay because it's a technology that isn't widely shared in the community, so if you want to know more about ZeroMQ, I recommend reading this article, which will give you a better introduction than me.

Today's example

The idea of today's example is to create a simple Api (server) that will receive a json property that will be the text and then we will send that same text to an app (worker) that will be responsible for calculating the length of this same string. Finally we will return the string length to the Api and then it is sent in the response body.

The framework I'm going to use is fiber and the ZeroMQ client that I'm going to use is zmq4.

Let's code

As you may have already understood, we are going to have two backends. One of the backends we will call a server, which will be our Api. The other backend will be the worker, which will be our small microservice.

First and foremost, let's install our dependencies:

go get github.com/gofiber/fiber/v2
go get github.com/pebbe/zmq4
Enter fullscreen mode Exit fullscreen mode

Now let's create a simple API:

// @/server/server.go
package main

import "github.com/gofiber/fiber/v2"

func main() {
    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("Hello there 👋")
    })

    app.Listen(":3000")
}
Enter fullscreen mode Exit fullscreen mode

We can start by working on our struct of the data coming from the request body, according to the example it should look like this:

type Input struct {
    Text string `json:"text"`
}
Enter fullscreen mode Exit fullscreen mode

Now we can import zmq4 into our project and let's create our client.

// @/server/server.go
package main

import (
    "github.com/gofiber/fiber/v2"
    zmq "github.com/pebbe/zmq4"
)

type Input struct {
    Text string `json:"text"`
}

func main() {
    app := fiber.New()
    zctx, _ := zmq.NewContext()

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Then we will create our ZeroMQ socket of the Request type and we will accept connections through an address defined by us.

// @/server/server.go
package main

import (
    "github.com/gofiber/fiber/v2"
    zmq "github.com/pebbe/zmq4"
)

type Input struct {
    Text string `json:"text"`
}

func main() {
    app := fiber.New()
    zctx, _ := zmq.NewContext()

    s, _ := zctx.NewSocket(zmq.REQ)
    s.Bind("tcp://*:6666")

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Then, in our endpoint, we will change the http verb from Get to Post and then we will parse the data coming from the request body using the c.BodyParser() function.

// @/server/server.go
package main

import (
    "github.com/gofiber/fiber/v2"
    zmq "github.com/pebbe/zmq4"
)

type Input struct {
    Text string `json:"text"`
}

func main() {
    app := fiber.New()
    zctx, _ := zmq.NewContext()

    s, _ := zctx.NewSocket(zmq.REQ)
    s.Bind("tcp://*:6666")

    app.Post("/", func(c *fiber.Ctx) error {
        input := new(Input)
        if err := c.BodyParser(input); err != nil {
            panic(err)
        }
        // ...
        return c.SendString("Hello there 👋")
    })

    app.Listen(":3000")
}
Enter fullscreen mode Exit fullscreen mode

Now we have to send the text to our websocket.

// @/server/server.go
package main

import (
    "github.com/gofiber/fiber/v2"
    zmq "github.com/pebbe/zmq4"
)

type Input struct {
    Text string `json:"text"`
}

func main() {
    app := fiber.New()
    zctx, _ := zmq.NewContext()

    s, _ := zctx.NewSocket(zmq.REQ)
    s.Bind("tcp://*:6666")

    app.Post("/", func(c *fiber.Ctx) error {
        input := new(Input)
        if err := c.BodyParser(input); err != nil {
            panic(err)
        }

        s.Send(input.Text, 0)
        // ...
        return c.SendString("Hello there 👋")
    })

    app.Listen(":3000")
}
Enter fullscreen mode Exit fullscreen mode

Now when we receive the message with the corresponding text length we will send it in the body of the response, if an error has occurred we will treat it.

// @/server/server.go
package main

import (
    "github.com/gofiber/fiber/v2"
    zmq "github.com/pebbe/zmq4"
)

type Input struct {
    Text string `json:"text"`
}

func main() {
    app := fiber.New()
    zctx, _ := zmq.NewContext()

    s, _ := zctx.NewSocket(zmq.REQ)
    s.Bind("tcp://*:6666")

    app.Post("/", func(c *fiber.Ctx) error {
        input := new(Input)
        if err := c.BodyParser(input); err != nil {
            panic(err)
        }

        s.Send(input.Text, 0)

        if msg, err := s.Recv(0); err != nil {
            panic(err)
        } else {
            return c.JSON(fiber.Map{"Length": msg})
        }
    })

    app.Listen(":3000")
}
Enter fullscreen mode Exit fullscreen mode

With this we have our server finished and now we can start working on our worker.

Let's import zmq4 into our project and let's create our client.

// @/worker/worker.go
package main

import (
    zmq "github.com/pebbe/zmq4"
)

func main() {
    zctx, _ := zmq.NewContext()

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Then we will create our ZeroMQ socket of the Reply type and we will accept connections through the address that we defined before.

// @/worker/worker.go
package main

import (
    zmq "github.com/pebbe/zmq4"
)

func main() {
    zctx, _ := zmq.NewContext()

    s, _ := zctx.NewSocket(zmq.REP)
    s.Connect("tcp://localhost:6666")

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Then let's create a for loop so that it calculates each of the messages we receive via our server.

As soon as we receive the message we will log it and then we will calculate the length of the text using the len() function, but we have to take into account that before sending the length of the text we have to convert the value to string.

For this we will use the strconv.Itoa() function. As follows:

// @/worker/worker.go
package main

import (
    "log"
    "strconv"

    zmq "github.com/pebbe/zmq4"
)

func main() {
    zctx, _ := zmq.NewContext()

    s, _ := zctx.NewSocket(zmq.REP)
    s.Connect("tcp://localhost:6666")

    for {
        if msg, err := s.Recv(0); err != nil {
            panic(err)
        } else {
            log.Printf("Received: %s\n", msg)
            s.Send(strconv.Itoa(len(msg)), 0)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now when testing our Api with a tool similar to Postman, you can send a json object in the request body with the same properties that are defined in our struct Input.

testing api

Then you should have something similar to this on your terminal:

terminal logs

Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! ✌️

Discussion (0)