~/DHRUVUpskilling
← board/Low-level design/Unsorted/LLD-1
Solved·23 Oct

Swiggy LLD

DifficultyMedium
PatternUnsorted
TrackLow-level design
tl;dr

I'll de discussing around HLD and LLD for Swiggy

full write-up

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

#MethodEndpointDetails
1POST/v1/user/registerRegister a new user
2GET/v1/restaurants/nearbyQuery: latitude, longitude, page, limit
3GET/v1/restaurants/searchQuery: title, menuItem, page, limit
4GET/v1/restaurants/{restId}Restaurant details and its menu
5POST/v1/cart/itemsBody: itemId, qty, restId. Add, update or delete an item. Returns cartId
6POST/v1/ordersBody: cartId. Returns orderId
7POST/v1/paymentsBody: paymentMetadata, orderId
8GET/v1/delivery/{orderId}/trackingLive 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, because orderId is created by the server and is the response.
  • We should also add GET /v1/orders so 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).
  • email should 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.restId is a foreign key to RestaurantDB.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 cardDb and List<itemID>. They are renamed here to CartDB and an items list that also stores qty, 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 status field (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 status in the metadata, such as PLACED, ACCEPTED, PICKED_UP and DELIVERED.

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 orderPlaced event 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

PartChoiceReason
Restaurant and menu dataRelational DBRestaurants and menus have relations
SearchElasticsearchText search and geo search are fast
Sync to searchCDC pipelineKeeps search data up to date automatically
ImagesS3 bucketCheap and scalable file storage
Service communicationKafkaAsync, so services are not tied together
Driver lookupGeo indexFast "nearby" queries
Search consistencyAP (available)Slightly old data is fine
Payment and order consistencyCP (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.
Swiggy LLD — pattern notes — Dhruv Parmar