Swiggy LLD
I'll de discussing around HLD and LLD for Swiggy
Swiggy: Low Level Design (LLD)
Overview
This document explains the design of a food delivery app like Swiggy. A user can register, find nearby restaurants, search by restaurant name or menu item, add items to a cart, pay, and track the delivery.
Functional Requirements
- A user can register in the application.
- The app lists all nearby restaurants based on the user's location.
- A user can search restaurants by title and by menu item.
- The restaurant page shows the full menu.
- A user can add items to a cart and pay to confirm the order. The order goes to the nearest restaurant.
- When the restaurant accepts the order, the app finds a nearby delivery partner based on the driver's location. It should optimize the delivery time.
- The user gets notifications at every stage, and can see past orders in the profile.
Non-Functional Requirements
1. Scale
- The app must handle a large number of users, restaurants and orders.
- Each service should scale on its own (horizontal scaling).
2. CAP Theorem
We use different choices for different parts of the app.
- Search and browsing: highly available (AP).
- Users should always be able to see restaurants.
- It is fine if the data is a few seconds old.
- Payments and orders: highly consistent (CP).
- A payment or an order must never be lost, duplicated or wrong.
- We prefer a correct answer over a fast one.
Core Entities
- User
- Restaurant
- Food Menu
- Delivery Agent / Partner
- Payment
API Design
| # | Method | Endpoint | Details |
|---|---|---|---|
| 1 | POST | /v1/user/register | Register a new user |
| 2 | GET | /v1/restaurants/nearby | Query: latitude, longitude, page, limit |
| 3 | GET | /v1/restaurants/search | Query: title, menuItem, page, limit |
| 4 | GET | /v1/restaurants/{restId} | Restaurant details and its menu |
| 5 | POST | /v1/cart/items | Body: itemId, qty, restId. Add, update or delete an item. Returns cartId |
| 6 | POST | /v1/orders | Body: cartId. Returns orderId |
| 7 | POST | /v1/payments | Body: paymentMetadata, orderId |
| 8 | GET | /v1/delivery/{orderId}/tracking | Live status and driver location |
Notes on the original list:
- APIs 4 and 5 in the original notes were the same (
GET v1/restaurants/{restId}), so they are merged into one.- For the order API, the body should hold the
cartId, becauseorderIdis created by the server and is the response.- We should also add
GET /v1/ordersso the user can see past orders in the profile.
High Level Design
Client
|
API Gateway
|
+-- User Service ---------> User DB
+-- Search Service -------> Elasticsearch <--(CDC)-- Restaurant DB
+-- Cart Service ---------> Cart DB
+-- Order Service --------> Order DB
+-- Payment Service ------> Payment DB --> Payment Gateway
+-- Delivery Service -----> Location store (geo index)
+-- Notification Service
Services talk to each other with Kafka events.
Images are stored in an S3 bucket.
Low Level Design
1. User Service
The user service stores user data after registration.
UserDB {
"userId": "",
"email": "",
"password": "",
"address": "",
"...metadata": ""
}
- Never store the plain password. Store a hash (for example bcrypt).
emailshould be unique.
2. Search Service
We need to find restaurants by location and title. For this we use Elasticsearch.
- Elasticsearch supports text search and geo queries (find items near a point).
- The source of truth is the Restaurant DB. It is a relational DB, because restaurants and menus are related.
- All images are stored in an S3 bucket. The DB keeps only the image URLs.
- We use a CDC (Change Data Capture) pipeline. When data changes in the tables below, the change is sent to Elasticsearch. This keeps the search data in sync.
- The CDC pipeline turns the relational tables into one search document, so we can search by restaurant title and by menu item together.
RestaurantDB {
"restId": "",
"name": "",
"address": "",
"status": "",
"geoLocation": "",
"img[]": "",
"...metadata": ""
}
Menu {
"menuId": "",
"itemId": "",
"restId": "",
"title": "",
"description": "",
"imageUrl": "",
"category": "",
"price": "",
"...metadata": ""
}
Menu.restIdis a foreign key toRestaurantDB.restId. One restaurant has many menu items.- Search is an AP feature. A short delay between the DB and Elasticsearch is acceptable.
3. Cart Service
- A cart can hold items from only one restaurant at a time.
- If the user adds an item from a different restaurant, we ask them to clear the old cart first.
CartDB {
"cartId": "",
"restId": "",
"userId": "",
"items": [{ "itemId": "", "qty": 0 }],
"...metadata": ""
}
Note: The original notes used
cardDbandList<itemID>. They are renamed here toCartDBand anitemslist that also storesqty, because the API sends a quantity.
4. Order and Payment Service
When the user places an order, the flow is:
- The order is created with the status
CREATED. - The request goes to the Payment Service.
- The Payment Service redirects the user to the Payment Gateway.
- When the payment finishes, we know if it succeeded or failed.
- We store the result in the Payment DB.
PaymentDB {
"paymentId": "",
"userId": "",
"orderId": "",
"amount": 0,
"currency": "",
"timestamp": "",
"...metadata": ""
}
- Payment and order data need strong consistency (CP). We use a relational DB with transactions.
- Use an idempotency key (for example
orderId) so a retry never charges the user twice. - Add a
statusfield (PENDING,SUCCESS,FAILED) in the metadata.
5. Order DB
OrderDB {
"orderId": "",
"userId": "",
"restId": "",
"items": [{ "itemId": "", "qty": 0 }],
"price": 0,
"currency": "",
"time": "",
"...metadata": ""
}
- Add an order
statusin the metadata, such asPLACED,ACCEPTED,PICKED_UPandDELIVERED.
6. Event Flow with Kafka
After the payment is complete, we notify the user and the restaurant. We use Kafka so services talk to each other asynchronously. This way, one slow service does not block the others.
Topic: orderPlaced
When an order is placed, an event goes into this topic. Then these things happen:
- Order consumer service: Updates the order in the Order DB.
- Notification service: Tells the user that the order is placed.
- Restaurant acceptance service: Sends the order to the restaurant, which can accept or reject it.
Topic: orderStatus
Every status change is sent as an event in this topic.
- When the restaurant accepts, the Delivery Partner service starts to find a driver.
- Each new status triggers a notification to the user.
- The status is also saved in the Order DB, so it shows in the order history.
7. Delivery Partner Service
After the restaurant accepts the order, we find a delivery partner.
- Drivers send their location often (for example every few seconds).
- We store the locations in a geo index (for example Redis GEO or a geohash index), so we can quickly find drivers near the restaurant.
- To optimize the delivery time, we do not pick only the closest driver. We rank drivers using:
- Distance from the restaurant.
- Estimated travel time (traffic).
- Whether the driver is free, and how many orders they carry.
- We send the request to the best driver. If they reject or do not answer in time, we try the next one.
- The user can follow the driver with
GET /v1/delivery/{orderId}/tracking.
8. Notification Service
- It listens to the Kafka topics (
orderPlaced,orderStatus). - It sends push notifications, SMS or email to the user at every stage:
- Order placed.
- Restaurant accepted.
- Driver assigned.
- Order picked up.
- Order delivered.
- Past orders come from the Order DB through
GET /v1/orders.
Order Flow (Step by Step)
- The user registers and logs in.
- The user sees nearby restaurants (Search Service).
- The user opens a restaurant and sees its menu.
- The user adds items to the cart (Cart Service).
- The user places the order and pays (Order + Payment Service).
- The
orderPlacedevent is sent to Kafka. - The order is saved, the user is notified, and the restaurant gets the order.
- The restaurant accepts the order.
- The Delivery Service finds the best nearby driver.
- The driver delivers the food. The user gets notifications and can track the order.
- The order shows in the user's past orders.
Design Choices Summary
| Part | Choice | Reason |
|---|---|---|
| Restaurant and menu data | Relational DB | Restaurants and menus have relations |
| Search | Elasticsearch | Text search and geo search are fast |
| Sync to search | CDC pipeline | Keeps search data up to date automatically |
| Images | S3 bucket | Cheap and scalable file storage |
| Service communication | Kafka | Async, so services are not tied together |
| Driver lookup | Geo index | Fast "nearby" queries |
| Search consistency | AP (available) | Slightly old data is fine |
| Payment and order consistency | CP (consistent) | Money and orders must be correct |
Summary
- The app is split into small services: User, Search, Cart, Order, Payment, Delivery and Notification.
- Search uses Elasticsearch, which is kept in sync with the relational Restaurant DB by a CDC pipeline.
- A cart holds items from one restaurant only.
- Payments and orders are strongly consistent. Search is highly available.
- Kafka topics (
orderPlaced,orderStatus) connect the order, notification, restaurant and delivery services. - The delivery service uses a geo index and ranks drivers by distance, travel time and load to cut delivery time.
- Users get a notification at every stage and can see past orders in their profile.