Microservices Communication digested

Rene Kabanda
5 min readJan 5, 2023

--

https://learn.microsoft.com/en-us/dotnet/architecture/microservices/multi-container-microservice-net-applications/media/rabbitmq-event-bus-development-test-environment/rabbitmq-implementation.png

Microservices are a popular architectural style for building scalable and resilient applications. One of the key benefits of microservices is the ability to independently deploy and scale each microservice, allowing teams to work on different parts of the application simultaneously using the right tools (e.g programming languages) for each microservice.

However, this also adds an extra layer of complexity to the mix. These microservices need a means to communicate with each other in order to coordinate their actions and share data in a seamless manner.

There are several ways to achieve this, each with its own advantages and trade-offs. Let’s take a look at some of the most common approaches.

  1. HTTP requests

One of the most straightforward ways for microservices to communicate is through HTTP requests. This can be done using a library like axios or the built-in http module in a Node.js environment.

To make an HTTP request from one microservice to another, you will need to know the network address (e.g. IP address or hostname) and port number of the target microservice. Here is an example of how to make a GET request using axios:

const axios = require('axios');

async function getData() {
try {
const response = await axios.get('http://example.com:3000/data');
console.log(response.data);
} catch (error) {
console.error(error);
}
}

getData();

2. gRPC

gRPC is an open-source high-performance RPC framework created by Google in 2016. It uses protocol buffers as its data interchange format. Protocol Buffers is language and platform agnostic mechanism for encoding structured data.

But what is RPC?

RPC stands for Remote Procedure Call (RPC). Simply put, it is a way for a computer program to ask another program, located on a different computer, to do something for it. The requesting program is called the client, and the one that does the work is called the server.

Here’s a simple example to illustrate this:

Imagine you have a smartphone app that allows you to book a ride with a ride-sharing service. The app is the client, and the server is the collection of the servers that power the ride-sharing service. When you open the app and request a ride, the app sends a message (a request) to the server, asking it to find a nearby driver and assign them to your ride. The server receives the request, processes it, and sends a response back to the app with the details of the assigned driver and the estimated time of arrival.

RPC works in a similar way. It typically uses a client-server model, with the calling client issuing a request to a remote server to execute a specified procedure, and the server returning the results to the client. The client and server exchange messages in a predefined format to request and provide service.

RPC technology is used in a variety of contexts, including distributed computing and inter-process communication (IPC). It is a key component of many software systems, including file systems, databases, and message-oriented middleware.

To use gRPC, you will need to define a service interface in a .proto file, and use a tool like the grpc package to generate the necessary code for your service.

Here is an example of a simple service definition in a .proto file:

syntax = "proto3";

service GreetingService {
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
string name = 1;
}

message HelloResponse {
string message = 1;
}

To implement the service in Node.js, you can use the grpc package as follows:

const grpc = require('grpc');

const server = new grpc.Server();

server.addService(GreetingService.service, {
sayHello(call, callback) {
const response = { message: `Hello, ${call.request.name}!` };
callback(null, response);
}
});

server.bind('0.0.0.0:3000', grpc.ServerCredentials.createInsecure());
server.start();

To call the service from another microservice, you can use the generated client code as follows:

const grpc = require('grpc');
const client = new GreetingService('localhost:3000', grpc.credentials.createInsecure());

client.sayHello({ name: 'John' }, (error, response) => {
if (error) {
console.error(error);
} else {
console.log(response.message);
}
});

3. Message queue

Before we talk about message queues, let’s first define 3 key components:

  • A producer: a user application that sends messages.
  • A consumer: a user application that receives messages.
  • A queue: a user application that stores messages.

A message queue is then a communication platform that allows producers and consumers to exchange messages in a decoupled manner. Producers send messages to the queue, which stores them until they are consumed by a consumer. This allows producers and consumers to operate at different scales, and enables asynchronous communication between them.

Again, let’s use a real world scenario to illustrate this.

Imagine you are a customer service representative at a large online retailer. Your job is to answer customer inquiries through a chat application. When a customer sends you a message, the chat application adds it to a message queue. The message queue is like a virtual queue of customer messages waiting to be answered.

Meanwhile, you and your co-workers are constantly checking the message queue for new messages. When you see a new message, you take it off the queue and start working on it. You might need to check the customer’s order history, consult with a colleague, or do some research before you can provide a satisfactory answer.

As you work on the message, other customers might send more messages to the queue. Your co-workers might also be working on their own messages, so the queue might grow or shrink as messages are added or removed.

In this example, the message queue acts as a buffer, allowing customers to send messages even if you and your co-workers are busy. It also decouples the customers from the customer service team, allowing you to operate independently and at different scales.

Some popular message queue systems or brokers include RabbitMQ and Apache Kafka.

To use a message queue, you will need to set up and configure the message queue server, and use a client library in your microservices to send and receive messages.

Here is an example of how to send a message using the amqplib library in Node.js.

Before we send messages though, we need a broker. In this example we will use RabbitMQ which you can easily run locally using the following Docker command. You need Docker to run this of course. If you don’t have Docker installed on your machine, you can follow the instructions here to install it.

docker run -d --name amqp.test -p 5672:5672 rabbitmq

It is also possible to use an instance hosted elsewhere but that’s beyond the scope of this post.

To create a producer in Node.js:

const amqp = require('amqplib');

async function sendMessage(message) {
try {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
const queue = 'my-queue';

channel.assertQueue(queue, { durable: false });
channel.sendToQueue(queue, Buffer.from(message));

console.log(`Sent message: ${message}`);
} catch (error) {
console.error(error);
}
}

sendMessage('Hello, world!');

To receive messages, you can use the consume method of the channel object:

channel.consume(queue, (msg) => {
console.log(`Received message: ${msg.content.toString()}`);
}, { noAck: true });

There are many other options and configurations available when using message queues, so be sure to consult the documentation of your chosen message queue system for more information.

Conclusion

As we have seen, there are several ways to enable communication between microservices. The choice of method or a combination of methods to adopt for your microservices communication will depend on your project’s requirements and your team’s needs. By choosing the right communication method for your specific use case, you can build a scalable and resilient application that is easy to maintain and evolve.

--

--

Rene Kabanda
Rene Kabanda

No responses yet