A distributed message-based food ordering system developed with RabbitMQ, Node.js, Express and MongoDB.
Head over to my article - Developing Message-Based Applications with RabbitMQ - published on The Startup to read more!
Burgernaut is a distributed food ordering platform that allows clients to place orders and track them. It consists of three integral components - order-service
, restaurant-service
and email-service
. The order-service
exposes REST endpoints that allow clients to fetch the food menu, place an order and track the order in real-time. Once the client places an order, the order-service
persists the order details on a MongoDB server, publishes it to a RabbitMQ Exchange and returns a confirmation response which includes the order ID to track the order. The Exchange is configured with a fanout pattern which publishes the order to the two queues that have been bound to it - order.process
and order.confirmation
. While placing it on the queue, the 'status' field of the order holds the value 'pending'. A restaurant-service
consumes the order from the order.process
queue and an email-service
consumes the order from the order.confirmation
queue. Once the restaurant-service
consumes the order, it modifies the 'state' of the order to 'accepted' in the database. The email-service
on the other hand sends an order confirmation to the email address specified in the order. After a pre-defined time period, the restaurant-service
modifies the 'state' to 'delivered' in the database indicating that the order has been delivered.
Burgernaut uses a message-based architecture to enhance scalability, flexibility and loose coupling among the various services it comprises. RabbitMQ, by default, ensures that all the messages consumed from a queue are distributed in a round-robin fashion. Since the restaurant-service
and email-service
consume from these queues, they can be independently scaled - though physically, scaling out the restaurant-service would mean opening new restaurants. The services are configured to process only a limited number of orders at a time to mimic actual business since restaurants have limited staff. The remaining orders wait on the queue to be consumed. The restaurant-service
sends an 'acknowledge' to RabbitMQ once the order is fully processed - delivered, in our case - to prevent it from being added back onto the queue. If one of the restaurants stop functioning say, due to a power loss, RabbitMQ would be aware that the tcp connection between the restaurant-service and the Exchange has been closed and any order that was being processed is automatically added back onto the queue; ready to be consumed by another restaurant-service. This enhances the fault-tolerance of the system. The number of orders that can be handled by the restaurant-service
and the amount of time it takes to process an order can be configured with their respective environment variables at the time of deployment.
As is evident, the system required asynchronous communication for its components. This is difficult to achieve with REST or RPC based systems as they are inherently blocking in nature. The system also required a highly-available and fault-tolerant queueing service for queueing the order. This prevents overwhelming the downstream components during peak time. RabbitMQ aids in satisfying these requirements. It also enhances the user experience. Instead of rejecting an order at peak load intervals, the order gets queued and a response is sent immediately to the client. As soon as a restaurant is available, it will consume it. Simply put, it tells the client - "Hey, we got your order and are working on it. Here's an order confirmation ID to track it."
If you have Docker installed in your computer, you do not need to have Node.js, RabbitMQ or MongoDB installed. You can run the application with a single command:
$ docker-compose up
Note: email-service requires a valid email ID and password to send order confirmations. Update them in the environment section of email-service in docker-compose.yml. The application can be tweaked with the environment variables defined in this file.
To scale each component independently, use:
$ docker-compose up --scale <service-name>=<number-of-containers>
For example, use the below command to replicate the architecture displayed in the system architecture diagram:
$ docker-compose up --scale restaurant-service=2
Without Docker, you need to install RabbitMQ, Node.js, NPM, MongoDB.
To install Node.js and NPM, refer to the documentation provided here.
To install MongoDB, refer to the documentation provided here.
To install RabbitMQ, refer to the documentation provided here.
To start RabbitMQ and MongoDB:
- Mac:
$ mongod --config /usr/local/etc/mongod.conf
$ rabbitmq-server
NOTE: Database and collection is automatically created with the first POST
request to /api/books
. No MongoDB configuration is required after installation - keeping MongoDB running is sufficient.
To get the Node server running locally:
npm install
to install all required dependencies from package.json:$ npm install
npm start
to start the local server:$ npm run start
npm run dev
to start the local server in development mode:$ npm run dev
Run the install
and start
commands for email-service, order-service and restaurant-service.
Returns json data containing menu.
-
URL
/api/menu
-
Method:
GET
-
URL Params None
-
Query Params
None
-
Data Params
None
-
Success Response:
- Code: 200
Content:{ "items": [ { "name": "burger", "price": 50 }, { "name": "fries", "price": 20 }, { "name": "coke", "price": 10 } ] }
- Code: 200
Returns json containing details of the placed order.
-
URL
/api/orders
-
Method:
POST
-
URL Params
None
-
Query Params
None
-
Data Params
Required:
items: [array]
name: [string]
quantity: [number]
email: [string]
-
Success Response:
- Code: 201 CREATED
Content:{ "total": 900, "status": "pending", "_id": "600307ce7a72ea0013a034f6", "items": [ { "name": "burger", "quantity": 10, "_id": "600307ce7a72ea0013a034f7" }, { "name": "fries", "quantity": 20, "_id": "600307ce7a72ea0013a034f8" } ], "email": "test@gmail.com", "createdAt": "2021-01-16T15:35:42.975Z", "__v": 0 }
- Code: 201 CREATED
Returns json data containing order details and the updated order status.
-
URL
/api/orders/:id
-
Method:
GET
-
URL Params
id: [string]
-
Query Params
None
-
Data Params
None
-
Success Response:
- Code: 200
Content:{ "total": 900, "status": "delivered", "_id": "600307ce7a72ea0013a034f6", "items": [ { "name": "burger", "quantity": 10 }, { "name": "fries", "quantity": 20 } ], "email": "test@gmail.com", "createdAt": "2021-01-16T15:35:42.975Z" }
- Code: 200
- Dhanush Kamath - dhanushkamath