Design a feature that accepts reviews from customers.
Let's discuss
Design a feature that accepts reviews from customers for an item ordered in a food delivery app. Also, show the average rating for each item under a restaurant. User should be able to post review for a particular item within a menu for a particular merchant. Other users should be able to upvote/downvote a review posted by a customer Customer will get a reward based on the “quality” of the review
FR
1.A customer can post a review for a specific item they have ordered from a particular restaurant (merchant).
2.Only customers who have ordered an item can post a review for that item.
3.A customer can edit or delete their review within a limited time window (e.g., 24 hours) after posting.
4.System should calculate and display the average rating for each item based on all valid reviews.
5.Users can upvote or downvote a review to express its helpfulness.
6.Each review will have a quality score, determined by:
Number of upvotes/downvotes
Length/content of review (e.g., NLP-based relevance)
Verified purchase status
NFR
System should be highly scalable to handle a large volume of reviews, ratings, and upvotes across millions of users and restaurants.
Posting or fetching reviews should have a response time of <200ms for 95th percentile requests.
System should be highly available availability to ensure users can post and view reviews anytime.
Review data must be eventually consistent across caches and databases.
System should ensure that a review is not lost once submitted (e.g., by using message queues or write-ahead logs).
System should be fault tolerant
Back of envelope calculation
Assumptions
50M daily active users (DAU)
10M daily orders
Each order has ~3 items on average
~20% of items get a review → 6M reviews/day
10x more reads than writes (people view reviews frequentlyReviews Written
6M reviews/day
= 6M / (24 × 3600)
≈ 70 reviews/sec
b. Upvotes/Downvotes
Assume each review gets 5 votes on average → 30M votes/day
= 30M / (24 × 3600)
≈ 350 votes/sec
✅ Total Write QPS ≈ 420 QPS2️⃣ Read QPS (Fetching reviews/ratings)
Each restaurant page shows avg rating per item
Suppose 5M users browse menus daily, each viewing ~10 items → 50M read ops/day
= 50M / (24 × 3600)
≈ 580 reads/sec
Also, reviews viewed explicitly by users → ~3x more reads → +1740 reads/sec
✅ Total Read QPS ≈ 2300 QPS
(Read-heavy system: ~85% reads)3️⃣ Storage Estimation
a. Review Storage
Each review: ~500 bytes (text, metadata, rating, timestamps)
6M reviews/day → 6M × 500 bytes = 3 GB/day
b. Votes
Each vote: ~50 bytes
30M votes/day → 30M × 50 bytes = 1.5 GB/day
c. Total Daily Data = 4.5 GB/day
For 1 year retention:
4.5 GB/day × 365 ≈ 1.6 TB/yearAPI’s
1️⃣ POST /reviews
Purpose: Submit a review for an item ordered from a restaurant.
Request
{
“user_id”: “U12345”,
“order_id”: “O98765”,
“merchant_id”: “M4321”,
“item_id”: “I6789”,
“rating”: 4.5,
“review_text”: “Paneer tikka was delicious and well-cooked!”,
“images”: [”https://s3.bucket/reviews/img123.jpg”]
}
response {
“review_id”: “R001122”,
“status”: “success”,
“message”: “Review submitted successfully”
}
2️⃣ GET /reviews
Purpose: Fetch all reviews for a specific item under a merchant.
Supports pagination, sorting, and filtering.
Query Params
merchant_id=M4321
item_id=I6789
sort=most_helpful|recent|rating_desc|rating_asc
rating_filter=4,5
page=1
limit=10
response
{
“item_id”: “I6789”,
“merchant_id”: “M4321”,
“average_rating”: 4.2,
“total_reviews”: 256,
“reviews”: [
{
“review_id”: “R001122”,
“user_name”: “Shashank”,
“rating”: 5,
“review_text”: “Loved it! Crispy and flavorful.”,
“upvotes”: 120,
“downvotes”: 3,
“created_at”: “2025-10-06T10:30:00Z”
},
{
“review_id”: “R001145”,
“user_name”: “Ananya”,
“rating”: 3,
“review_text”: “Okay taste, too spicy for me.”,
“upvotes”: 25,
“downvotes”: 5,
“created_at”: “2025-10-05T18:10:00Z”
}
]
}
3️⃣ POST /reviews/vote
Purpose: Upvote or downvote a review.
Request
{
“user_id”: “U999”,
“review_id”: “R001122”,
“vote_type”: “upvote” // or “downvote”
}response
{
“status”: “success”,
“message”: “Your vote has been recorded”
}
4️⃣ PUT /reviews/{review_id}
Purpose: Edit an existing review (allowed only within 24 hours).
Request
{
“rating”: 4,
“review_text”: “Taste improved after my second order!”,
“images”: []
}
response {
“status”: “success”,
“message”: “Review updated successfully”
}
5️⃣ DELETE /reviews/{review_id}
Purpose: Delete a user’s own review.
Response
{
“status”: “success”,
“message”: “Review deleted successfully”
}
6️⃣ GET /ratings/average
Purpose: Get the average rating for all items under a merchant (for menu display).
Query Params
merchant_id=M4321
response
{
“merchant_id”: “M4321”,
“items”: [
{
“item_id”: “I6789”,
“item_name”: “Paneer Tikka”,
“average_rating”: 4.3,
“review_count”: 1200
},
{
“item_id”: “I6790”,
“item_name”: “Dal Makhani”,
“average_rating”: 4.7,
“review_count”: 890
}
]
}
7️⃣ GET /rewards/{user_id}
Purpose: View user’s reward details for quality reviews.
Response
7️⃣ GET /rewards/{user_id}
Purpose: View user’s reward details for quality reviews.
Response8️⃣ POST /reviews/report
Purpose: Report a review as inappropriate or spam.
Request
{
“user_id”: “U555”,
“review_id”: “R001122”,
“reason”: “Contains offensive language”
}
response
{
“status”: “success”,
“message”: “Review reported for moderation”
}
9️⃣ GET /merchant/insights
Purpose: Allow merchants to view analytics about reviews & ratings.
Query Params
merchant_id=M4321
time_range=last_30_days
Response
{
“merchant_id”: “M4321”,
“avg_rating_trend”: {
“2025-09-15”: 4.2,
“2025-09-22”: 4.3,
“2025-09-29”: 4.1
},
“top_items”: [
{”item_id”: “I6790”, “item_name”: “Dal Makhani”, “avg_rating”: 4.7},
{”item_id”: “I6789”, “item_name”: “Paneer Tikka”, “avg_rating”: 4.3}
],
“total_reviews”: 3200
}
| API | Method | Purpose |
| -------------------- | ------ | ---------------------------- |
| `/reviews` | POST | Submit a new review |
| `/reviews` | GET | Get all reviews for an item |
| `/reviews/vote` | POST | Upvote/Downvote a review |
| `/reviews/{id}` | PUT | Edit a review |
| `/reviews/{id}` | DELETE | Delete a review |
| `/ratings/average` | GET | Get average ratings per item |
| `/rewards/{user_id}` | GET | Get reward details |
| `/reviews/report` | POST | Report a review |
| `/merchant/insights` | GET | Merchant analytics |
Databases and their Schema
| Component | Database Type | Reason |
| --------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------ |
| `reviews`, `votes` | **NoSQL (Cassandra / DynamoDB)** or **MySQL (sharded by merchant_id)** | High write volume, fast reads for recent reviews |
| `item_avg_rating` | **Redis / MySQL** | Fast access to average ratings for menu display |
| `review_reports`, `rewards` | **MySQL / PostgreSQL** | Transactional consistency |
| Review images | **S3 / Object Store** | Efficient image storage and retrieval |
🧩 SQL Schema
-- ===============================
-- USERS TABLE (from user service)
-- ===============================
CREATE TABLE users (
user_id VARCHAR(64) PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(150),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ==================================
-- MERCHANT TABLE (from merchant svc)
-- ==================================
CREATE TABLE merchants (
merchant_id VARCHAR(64) PRIMARY KEY,
name VARCHAR(150),
location VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- ======================
-- ITEM TABLE (menu item)
-- ======================
CREATE TABLE items (
item_id VARCHAR(64) PRIMARY KEY,
merchant_id VARCHAR(64),
name VARCHAR(150),
description TEXT,
price DECIMAL(10,2),
FOREIGN KEY (merchant_id) REFERENCES merchants(merchant_id)
);
-- ========================
-- REVIEWS TABLE
-- ========================
CREATE TABLE reviews (
review_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
order_id VARCHAR(64) NOT NULL,
merchant_id VARCHAR(64) NOT NULL,
item_id VARCHAR(64) NOT NULL,
rating DECIMAL(2,1) CHECK (rating >= 1 AND rating <= 5),
review_text TEXT,
images JSON,
upvote_count INT DEFAULT 0,
downvote_count INT DEFAULT 0,
quality_score FLOAT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE (user_id, order_id, item_id),
INDEX idx_item (merchant_id, item_id),
INDEX idx_user (user_id),
FOREIGN KEY (merchant_id) REFERENCES merchants(merchant_id)
);
-- ===========================
-- VOTES TABLE (upvote/downvote)
-- ===========================
CREATE TABLE review_votes (
vote_id VARCHAR(64) PRIMARY KEY,
review_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
vote_type ENUM(’upvote’,’downvote’) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (review_id, user_id),
FOREIGN KEY (review_id) REFERENCES reviews(review_id)
);
-- =====================================
-- ITEM AVERAGE RATING TABLE (cacheable)
-- =====================================
CREATE TABLE item_avg_rating (
item_id VARCHAR(64) PRIMARY KEY,
merchant_id VARCHAR(64) NOT NULL,
average_rating DECIMAL(2,1) DEFAULT 0.0,
total_reviews INT DEFAULT 0,
last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (merchant_id) REFERENCES merchants(merchant_id)
);
-- ============================
-- REVIEW REPORTS TABLE
-- ============================
CREATE TABLE review_reports (
report_id VARCHAR(64) PRIMARY KEY,
review_id VARCHAR(64) NOT NULL,
user_id VARCHAR(64) NOT NULL,
reason VARCHAR(255),
status ENUM(’pending’,’reviewed’,’dismissed’) DEFAULT ‘pending’,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (review_id) REFERENCES reviews(review_id)
);
-- ============================
-- REVIEW REWARDS TABLE
-- ============================
CREATE TABLE review_rewards (
reward_id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
review_id VARCHAR(64) NOT NULL,
points_earned INT DEFAULT 0,
reason VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (review_id) REFERENCES reviews(review_id),
FOREIGN KEY (user_id) REFERENCES users(user_id)
);
Additional Notes
Indexes:
idx_item (merchant_id, item_id)→ fast fetch for all reviews of an itemidx_user (user_id)→ user review history lookup
Caching:
Store
item_avg_ratingin Redis for fast menu load times.Invalidate or update cache asynchronously after review write.
Storage Tiering:
Archive reviews older than 1 year to S3 or cold storage.
Async Updates:
Use Kafka topics like
review_created,vote_castfor async rating aggregation and reward computation.
Microservices and their interaction
🧭 Microservices and Their Interactions
1️⃣ Review Service
Responsibilities
Accept new reviews (validate order eligibility)
Edit/Delete reviews
Fetch reviews for an item (with pagination, filters)
Publish events:
review_created,review_updated,review_deleted
Interactions
Reads order info from Order Service (to verify valid purchase)
Writes review data to Review DB (MySQL / Cassandra)
Pushes events to Kafka for async aggregation and reward scoring
2️⃣ Vote Service
Responsibilities
Handle upvote/downvote actions
Ensure idempotent voting (one vote per user per review)
Update review’s upvote/downvote counters
Interactions
Writes to Votes DB
Publishes
vote_castevent → triggers Review Quality UpdaterUpdates cache (Redis) for real-time feedback
3️⃣ Rating Aggregator Service
Responsibilities
Consume
review_created,review_updated,review_deletedeventsRecompute average rating for each item
Update
item_avg_ratingin DB + Redis
Interactions
Reads/Writes from Item Average Rating Table
Pushes updates to Cache Service (Redis)
Event Example (Kafka)
{
“event”: “review_created”,
“item_id”: “I6789”,
“merchant_id”: “M4321”,
“rating”: 5,
“review_id”: “R001122”
}
4️⃣ Reward Service
Responsibilities
Listen to
review_createdandvote_casteventsCalculate review quality score (based on upvotes/downvotes, text length, etc.)
Award points to user if review quality threshold is met
Interactions
Writes reward data to Rewards DB
Exposes API
/rewards/{user_id}
Formula Example
quality_score = f(upvotes, downvotes, review_length, verified_order)
5️⃣ Report & Moderation Service
Responsibilities
Handle
/reviews/reportrequestsFlag inappropriate or spam reviews
Expose admin/moderation dashboard for manual review
Interactions
Writes to Review Reports Table
Integrates with Notification Service (alert moderators)
Can auto-hide reviews based on NLP/spam detection
6️⃣ Merchant Insights Service
Responsibilities
Aggregate review and rating analytics per merchant
Provide insights like:
Average rating trend
Top items
Common negative feedback
Interactions
Consumes data from Analytics Stream (Kafka → Data Warehouse)
Serves
/merchant/insightsAPI
7️⃣ Cache Layer (Redis)
Responsibilities
Store frequently accessed data:
item_avg_ratingtop 10 reviews per item
Expiry & invalidation triggered by
review_createdorvote_castevents
Example Key Structure
avg_rating:{merchant_id}:{item_id} = 4.5
top_reviews:{merchant_id}:{item_id} = [R001122, R001130, ...]
Object Storage (S3)
Responsibilities
Store review images uploaded by customers
URLs stored in
reviews.imagesJSON field
HLD
+--------------------+ +---------------------+
| User App (Client) | <--> | API Gateway |
+--------------------+ +----------+----------+
|
v
+---------------------+
| Review Service |
+---------+-----------+
| writes reviews
v
+-----------+
| Review DB |
+-----------+
|
+-----------------------+---------------------------+
| | |
v v v
+----------------+ +----------------+ +--------------------+
| Vote Service | | Reward Service | | Rating Aggregator |
+--------+-------+ +--------+-------+ +---------+----------+
| | |
Kafka “vote_cast” Kafka “review_created” Kafka “rating_update”
| | |
v v v
+-------------+ +--------------+ +---------------+
| Votes Table | | Rewards Table| | ItemAvgRating |
+-------------+ +--------------+ +---------------+
|
v
+-------------+
| Redis Cache |
+-------------+
|
v
+-------------+
| Menu/Review |
| Read APIs |
+-------------+
HLD



Good One!