Introduction
The microservice architecture is an approach to building scalable software by breaking it down into smaller and independent applications called services. It allows organizations to build resilient, flexible, easy-to-maintain, and scalable software to compete and meet growing demands.
In this article, we will be exploring the concept of Microservice Architecture, and implementing basic microservices-driven software using Node.js and RabbitMQ.
Fundamentals of Microservices Architecture
What are Microservices?
Microservices are a collection of individual and autonomous applications performing a well-defined function, which make up a piece of software. Applications built using this architectural style comprise of smaller independently deployable components, which communicate with each other through message brokers, such as RabbitMQ or other protocols like HTTP. This is a stark contrast to monolithic applications, which are built as a unified entity.
Microservices Architecture vs Monolithic Architecture
As we mentioned earlier, the microservice architecture is used to build software by breaking it down into individual components, which handle specific parts of the business logic, such as user authentication or notification. When a monolithic application is built, there is no segregation; all components are part of a single codebase.
An application built using the microservices architecture is highly scalable and easy to maintain since each service is independently deployed, maintained, and scaled. This approach also makes it modular, which means that changes in a component don’t affect the entire system and errors are localized to the specific service.
In essence, microservices are resilient since services run in isolation and independent of each other, failure of a service doesn’t bring down the system. Unlike the monolithic application where an error in the codebase can bring down the entire application.
Microservices are also technology agnostic, which means each service can be built with a technology stack of choice. This brings a high level of flexibility when building solutions.
Concept of Message Brokers
A message broker is a software that facilitates communication between services. It ensures that the microservices exchange messages and information reliably between each other. It facilitates communication between services even when they are written in different languages or frameworks by providing a standardized means of handling the flow of data.
Message brokers work by managing the exchange of messages between a Producer and a Consumer. This can be implemented by either a Publish-Subscribe (Pub-Sub) or Message Queue model.
In the Pub-Sub system, the producer sends a message to a channel, where subscribed consumers can receive that message. In a Message Queue model, the producer sends messages to a specific queue, where a single consumer consumes it, after consumption, the message is removed from the queue.
Implementing Microservices with Node.js and RabbitMQ
In this section, we are going to be building a simple software implementing the microservice architecture using RabbitMQ and Node.js. We would be building three services: the Product, Order, and Notification services.
Setting Up the Project
First, create a directory for your application, then initialize the project and install dependencies:
mkdir ./path_to_project_directorynpm init -ynpm install express amqplib dotenv
RabbitMQ Setup
You can set up RabbitMQ using Docker locally via an installation file, or by using a managed service like CloudAMPQ.
CloudAMPQ is a fully managed RabbitMQ service, which automates setup, scalability, and operation. CloudAMPQ has various subscription plans including a free plan.
Docker Setup
Let’s pull the RabbitMQ docker image:
docker pull rabbitmq
Now run:
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
This runs the RabbitMQ server on port 5672 and the management interface on 15672 (default username/password is guest).
Local Installation
Download the RabbitMQ installation file alongside the OS-specific instructions. Run the installer, and follow the installation instructions. Ensure the server is running:
Windows:
rabbitmq-service start
Linux:
systemctl status rabbitmq-server.service
then
sudo service rabbitmq-server restart
MacOS:
brew services start rabbitmq
CloudAMPQ Setup
This is the easiest approach to setting up RabbitMQ, which we will be using in this article. Head over to the CloudAMPQ signup page, and create an account. Create an instance; give it a name, select the free plan, select a region, and complete the setup.
After setting up your RabbitMQ copy your connection URL amqps://username:[email protected]/vhost
Creating the Product Service
The product service manages product creation and publishes the message to the product_queue to be consumed by the Order Service.
Create the product service directory:
mkdir product_service
cd product_service
Create an index.js file.
Copy the following code:
require('dotenv').config(); // Load environment variables
const express = require('express');
const amqp = require('amqplib/callback_api');
const app = express();
app.use(express.json());
let channel;
// Connect to RabbitMQ
amqp.connect(process.env.RABBITMQ_URL, (err, conn) => {
if (err) throw err;
conn.createChannel((err, ch) => {
if (err) throw err;
channel = ch;
channel.assertQueue('product_queue', { durable: false });
});
});
// Product endpoint
app.post('/product', (req, res) => {
const product = req.body;
// Publish product creation event to RabbitMQ
channel.sendToQueue('product_queue', Buffer.from(JSON.stringify(product)));
res.status(201).send(`Product created: ${product.name}`);
});
// Use port from environment variable
app.listen(process.env.PRODUCT_SERVICE_PORT, () => {
console.log(`Product service running on port ${process.env.PRODUCT_SERVICE_PORT}`);
});
Create a .env file and add the connection details:
RABBITMQ_URL= *your connection URL*
Add PRODUCT_SERVICE_PORT=3000
to the .env file.
run node index.js
Creating the Order Service
This listens for product creation event, and processes orders, and publishes the order to the order_queue, which would be consumed by the Notification Service.
Create the order service directory from your project root directory:
mkdir order_service
cd order_service
Create an index.js file.
Write the following code:
require('dotenv').config();
const express = require('express');
const amqp = require('amqplib/callback_api');
const app = express();
app.use(express.json());
let channel;
// Connect to RabbitMQ
amqp.connect(process.env.RABBITMQ_URL, (err, conn) => {
if (err) throw err;
conn.createChannel((err, ch) => {
if (err) throw err;
channel = ch;
channel.assertQueue('product_queue', { durable: false });
// Consume product creation events
channel.consume('product_queue', (msg) => {
const product = JSON.parse(msg.content.toString());
console.log(`Received product: ${product.name}`);
// Handle product logic (e.g., create order)
}, { noAck: true });
});
});
// Order endpoint
app.post('/order', (req, res) => {
const order = req.body;
// Publish order event to RabbitMQ
channel.sendToQueue('order_queue', Buffer.from(JSON.stringify(order)));
res.status(201).send(`Order created for product: ${order.product}`);
});
// Use port from environment variable
app.listen(process.env.ORDER_SERVICE_PORT, () => {
console.log(`Order service running on port ${process.env.ORDER_SERVICE_PORT}`);
});
Create a .env file, and add the connection details:
RABBITMQ_URL= *your connection URL*
Add ORDER_SERVICE_PORT=3001
to the .env file.
Run node index.js
Creating the Notification Service
This handle sending notifications when orders are placed.
Create the notification service directory:
mkdir notification_service
cd notification_service
Create an index.js file
Write the following code:
require('dotenv').config();
const express = require('express');
const amqp = require('amqplib/callback_api');
const app = express();
app.use(express.json());
let channel;
// Connect to RabbitMQ
amqp.connect(process.env.RABBITMQ_URL, (err, conn) => {
if (err) throw err;
conn.createChannel((err, ch) => {
if (err) throw err;
channel = ch;
channel.assertQueue('product_queue', { durable: false });
// Consume order events
channel.consume('order_queue', (msg) => {
const order = JSON.parse(msg.content.toString());
console.log(`Notification: Order placed for product ${order.product}`);
// Handle notification logic (e.g., send email)
}, { noAck: true });
});
});
app.listen(process.env.NOTIFICATION_SERVICE_PORT, () => {
console.log(`Notification service running on port ${process.env.NOTIFICATION_SERVICE_PORT}`);
});
Create a .env file, and add the connection details:
RABBITMQ_URL= *your connection URL*
Add NOTIFICATION_SERVICE_PORT=3002
to the .env file.
run node index.js
Testing the Setup
We are going to be using Postman to test our services.
Product Service
Order Service
Notification Service
Messages are logged to the console based on the events from the order_queue
Conclusion
By implementing the microservice architecture, organizations have been able to build scalable and resilient applications. The development and maintenance process becomes streamlined due to increased modularity, and resources are effectively utilized.
This repo contains the code.
If you have any questions, please, drop them in the comments below.
Cover Image by macrovector on Freepik