This article is to simplify and do a step by step tutorial to make a microservices app
In this tutorial, I'm going to use
- Node.js as runtime environment.
- Typescript instead of Javascript for better development experience.
- Express.js for the backend.
- Rabbitmq as a message broker.
To find the final code, you can find it on Github
Before typing code, why Microservices?
Let's say we have to services, In a Monolithic Architecture you would have only one repository and all your services interact through the same instance, if any service caused the instance to crash, the whole application will crash.
While in a Microservices Architecture each will work a whole unit and on interacting with other services, it uses message brokers like Rabbitmq to send a message to other services.
Now consider a warehouse service (consumer) that really depends on an order service (publisher), where whenever an order is submitted the warehouse is notified and starts the shipping process, in this architecture, if the order service caused a crash, the warehouse won't be affected and will keep working, even if the warehouse service was out when an order is submitted, the message broker will keep the message and on the startup of the warehouse service, it will assert that in knows about this message.
How to implement it?
First, the folder structure, consider making a directory called online-ordering-microservices, with two folders inside [order, warehouse]
The .gitingore file to ignore the node_modules inside each service
Second, setup rabbitmq as a message broker
Run docker container run --name rabbitmq --detach -p 5672:5672 rabbitmq
This will run a container from the image rabbitmq, on a detached mode and expose the port 5672 which is the default port for rabbitmq and give this container a name rabbitmq
Third, setup each service.
Run npm install express @types/express amqplib @types/amqplib
to install the necessary dependencies.
order/index.ts
import amqplib, { Channel, Connection } from 'amqplib'
import express, { Request, Response } from 'express'
const app = express()
// parse the request body
app.use(express.json())
// port where the service will run
const PORT = 9005
// rabbitmq to be global variables
let channel: Channel, connection: Connection
connect()
// connect to rabbitmq
async function connect() {
try {
// rabbitmq default port is 5672
const amqpServer = 'amqp://localhost:5672'
connection = await amqplib.connect(amqpServer)
channel = await connection.createChannel()
// make sure that the order channel is created, if not this statement will create it
await channel.assertQueue('order')
} catch (error) {
console.log(error)
}
}
app.post('/orders', (req: Request, res: Response) => {
const data = req.body
// send a message to all the services connected to 'order' queue, add the date to differentiate between them
channel.sendToQueue(
'order',
Buffer.from(
JSON.stringify({
...data,
date: new Date(),
}),
),
)
res.send('Order submitted')
})
app.get('*', (req: Request, res: Response) => {
res.status(404).send('Not found')
})
app.listen(PORT, () => {
console.log(`Server running on ${PORT}`)
})
warehouse/index.ts
import amqplib, { Channel, Connection } from 'amqplib'
import express, { Request, Response } from 'express'
const app = express()
// parse the request body
app.use(express.json())
// port where the service will run
const PORT = 9005
// rabbitmq to be global variables
let channel: Channel, connection: Connection
connect()
async function connect() {
try {
const amqpServer = 'amqp://localhost:5672'
connection = await amqplib.connect(amqpServer)
channel = await connection.createChannel()
// consume all the orders that are not acknowledged
await channel.consume('order', (data) => {
console.log(`Received ${Buffer.from(data!.content)}`)
channel.ack(data!);
})
} catch (error) {
console.log(error)
}
}
app.get('*', (req: Request, res: Response) => {
res.status(404).send('Not found')
})
app.listen(PORT, () => {
console.log(`Server running on ${PORT}`)
})
Now, if you run both apps and made a post request to http://localhost:9005/orders, you will get a message in the warehouse service, more importantly, if you made a request while the warehouse service is not running and started the warehouse service, it will receive that message, and actually will keep receiving it untill it acknowledges it.
I hope you liked this tutorial, and see you in another ones.
Top comments (2)
What if we wanted a reply from warehouse how to setup that .
I am yet to try it out too. But logically, since. we are just passing streams of data to a queue, you can assert a queue in the warehouse service and run the logic again after the initial data from order is consumed.