HLD: Aggregation System for Vending Machines
Let's look into functional requriement
FR
1.The system should allow registering a new vending machine.
2.The system should maintain a catalog of products per vending machine
3.The system should collect sales events from vending machines in near real time.
4.The system should aggregate sales data at different levels:
Per vending machine
Per location
Per product
Per time window (hour/day/week/month)
5.The system should calculate:
Total revenue per machine
Revenue per product
Revenue per location
6.The system should automatically detect:
Low stock conditions
Out-of-stock products
Expired products
7.The system should ingest machine telemetry data such as:
Power status
Temperature
Door open/close
Network connectivity
NFR
1.System should be highly scalable to support millions of machines and thousands of events per region.
2.System should be highly available
3.System should be fault tolerant
4.Event ingestion latency should be <200ms, near real time aggregation should be <5ms.
5.Transactional data must be strongly consistent across all vending machines,aggregated metrics may be eventually consistent.
6.System should be highly durbale
API’s
API Design – Vending Machine Aggregation System
1. Authentication & Authorization
(All APIs require auth unless mentioned)
POST /auth/token
Issues access token for machines or users
request {
"client_id": "machine_123",
"client_secret": "******",
"grant_type": "client_credentials"
}
response
{
"access_token": "jwt-token",
"expires_in": 3600
}
2. Vending Machine Management APIs
POST /machines
Register a new vending machine
{
"machineId": "VM_1001",
"location": {
"city": "Bangalore",
"building": "Tech Park A",
"lat": 12.9716,
"lng": 77.5946
},
"paymentMethods": ["CASH", "CARD", "UPI"]
}
GET /machines/{machineId}
Fetch machine details
PATCH /machines/{machineId}
Update machine metadata or status
{
"status": "UNDER_MAINTENANCE"
}
DELETE /machines/{machineId}
Decommission machine
3. Product & Inventory APIs
POST /machines/{machineId}/products
Add product to machine
{
"productId": "P100",
"name": "Coke",
"price": 50,
"quantity": 30,
"expiryDate": "2025-06-01"
}
PATCH /machines/{machineId}/products/{productId}
Update price or stock
{
"price": 55,
"quantity": 25
}
GET /machines/{machineId}/inventory
Fetch current inventory state
4. Sales / Transaction Ingestion APIs
(High-throughput, machine-facing)
POST /events/sales
Ingest sale event
(Idempotent API)
Headers
Idempotency-Key: txn_12345
request
{
"transactionId": "txn_12345",
"machineId": "VM_1001",
"productId": "P100",
"quantity": 1,
"amount": 50,
"paymentMethod": "UPI",
"timestamp": "2025-01-10T10:15:30Z"
}
response
{
"status": "ACCEPTED"
}
POST /events/batch
Batch ingestion (for offline buffering machines)
{
"events": [ {...}, {...} ]
}
5. Machine Telemetry APIs
POST /events/telemetry
Ingest telemetry data
{
"machineId": "VM_1001",
"temperature": 5.4,
"powerStatus": "ON",
"doorStatus": "CLOSED",
"timestamp": "2025-01-10T10:16:00Z"
}
6. Fault & Incident APIs
POST /events/faults
Report machine fault
{
"machineId": "VM_1001",
"faultType": "PAYMENT_FAILURE",
"description": "Card reader not responding",
"timestamp": "2025-01-10T10:17:00Z"
}
GET /faults?status=OPEN&city=Bangalore
List active faults
PATCH /faults/{faultId}
Update fault status
{
"status": "RESOLVED"
}
Databases and their Schema
1. Core Metadata Tables
vending_machines
CREATE TABLE vending_machines (
machine_id VARCHAR(64) PRIMARY KEY,
city VARCHAR(64),
building VARCHAR(128),
latitude DECIMAL(9,6),
longitude DECIMAL(9,6),
status VARCHAR(32), -- ACTIVE, INACTIVE, MAINTENANCE
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
products
(Global product catalog)
CREATE TABLE products (
product_id VARCHAR(64) PRIMARY KEY,
name VARCHAR(128),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
machine_inventory
(Product state per machine)
CREATE TABLE machine_inventory (
machine_id VARCHAR(64),
product_id VARCHAR(64),
price DECIMAL(10,2),
quantity INT,
expiry_date DATE,
low_stock_limit INT DEFAULT 5,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (machine_id, product_id),
FOREIGN KEY (machine_id) REFERENCES vending_machines(machine_id),
FOREIGN KEY (product_id) REFERENCES products(product_id)
);
2. Event Tables (Append-Only)
sales_events
(Raw immutable sales events)
CREATE TABLE sales_events (
transaction_id VARCHAR(64) PRIMARY KEY,
machine_id VARCHAR(64),
product_id VARCHAR(64),
quantity INT,
amount DECIMAL(10,2),
payment_method VARCHAR(32),
event_time TIMESTAMP,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
telemetry_events
CREATE TABLE telemetry_events (
id BIGSERIAL PRIMARY KEY,
machine_id VARCHAR(64),
temperature DECIMAL(5,2),
power_status VARCHAR(32),
door_status VARCHAR(32),
event_time TIMESTAMP
);
fault_events
CREATE TABLE fault_events (
fault_id BIGSERIAL PRIMARY KEY,
machine_id VARCHAR(64),
fault_type VARCHAR(64),
description TEXT,
status VARCHAR(32), -- OPEN, RESOLVED
event_time TIMESTAMP,
resolved_at TIMESTAMP
);
3. Aggregated Tables (Pre-Computed)
daily_machine_sales
CREATE TABLE daily_machine_sales (
machine_id VARCHAR(64),
sales_date DATE,
total_transactions INT,
total_quantity INT,
total_revenue DECIMAL(12,2),
PRIMARY KEY (machine_id, sales_date)
);
daily_product_sales
CREATE TABLE daily_product_sales (
product_id VARCHAR(64),
sales_date DATE,
total_quantity INT,
total_revenue DECIMAL(12,2),
PRIMARY KEY (product_id, sales_date)
);
daily_location_sales
CREATE TABLE daily_location_sales (
city VARCHAR(64),
sales_date DATE,
total_revenue DECIMAL(12,2),
PRIMARY KEY (city, sales_date)
);
4. Alerts & Monitoring
alerts
CREATE TABLE alerts (
alert_id BIGSERIAL PRIMARY KEY,
machine_id VARCHAR(64),
product_id VARCHAR(64),
alert_type VARCHAR(64), -- LOW_STOCK, OUT_OF_STOCK
status VARCHAR(32), -- OPEN, CLOSED
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
5. Users & Access Control
users
CREATE TABLE users (
user_id BIGSERIAL PRIMARY KEY,
name VARCHAR(128),
role VARCHAR(32), -- ADMIN, OPS, FINANCE
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
6. Audit Logs
audit_logs
CREATE TABLE audit_logs (
id BIGSERIAL PRIMARY KEY,
entity_type VARCHAR(64),
entity_id VARCHAR(64),
action VARCHAR(64),
performed_by VARCHAR(64),
performed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Microservices Architecture – Vending Machine Aggregation System
1. Microservices Breakdown
1. Machine Management Service
Responsibility
Register / update / decommission vending machines
Maintain machine metadata (location, status)
Owns DB
vending_machines
APIs
POST /machinesGET /machines/{id}PATCH /machines/{id}
2. Product & Inventory Service
Responsibility
Manage product catalog
Track inventory per machine
Update stock on sales
Owns DB
productsmachine_inventory
APIs
POST /machines/{id}/productsPATCH /machines/{id}/products/{pid}GET /machines/{id}/inventory
3. Sales Ingestion Service
Responsibility
High-throughput ingestion of sales events
Idempotency & validation
Push events to event stream
Owns DB
sales_events(raw)
APIs
POST /events/salesPOST /events/batch
Publishes Events
sales-eventstopic
4. Telemetry Service
Responsibility
Ingest machine health metrics
Track online/offline status
Owns DB
telemetry_events
APIs
POST /events/telemetry
Publishes Events
telemetry-eventstopic
5. Fault Management Service
Responsibility
Record machine faults
Track fault lifecycle
Owns DB
fault_events
APIs
POST /events/faultsGET /faultsPATCH /faults/{id}
6. Aggregation Service
Responsibility
Consume raw events
Compute time-windowed aggregations
Maintain pre-computed views
Owns DB
daily_machine_salesdaily_product_salesdaily_location_sales
Consumes Topics
sales-events
7. Analytics & Query Service
Responsibility
Serve read-heavy analytical queries
Query pre-aggregated data only
Owns DB
Read-only access to aggregation tables
APIs
GET /aggregations/salesGET /aggregations/products/top
8. Alerting Service
Responsibility
Detect low stock, offline machines
Generate alerts
Consumes Topics
sales-eventstelemetry-events
Owns DB
alerts
9. Reporting Service
Responsibility
Generate daily/monthly reports
Export CSV / JSON
APIs
GET /reports/*POST /reports/export
10. User & Auth Service
Responsibility
Authentication & RBAC
Issue access tokens
Owns DB
users
11. Audit Service
Responsibility
Record system & user actions
Compliance & traceability
Owns DB
audit_logs
Service Interaction (End-to-End Flows)
Flow 1: Sale Happens on Vending Machine
Vending Machine
↓
Sales Ingestion Service
↓ (persist raw event)
sales_events table
↓
Publish → sales-events topic
Downstream consumers:
Aggregation Service → updates daily_* tables
Inventory Service → decrements stock
Alerting Service → checks low stock
➡️ No synchronous calls between services
Flow 2: Inventory Update
Inventory Service
↓
Update machine_inventory
↓
Emit inventory-updated event (optional)
Flow 3: Machine Telemetry
Vending Machine
↓
Telemetry Service
↓
telemetry-events topic
↓
Alerting Service → offline alerts
Flow 4: Analytics Query
Client / Dashboard
↓
Analytics Service
↓
Query pre-aggregated tables
↓
Fast response (<2s)
Flow 5: Fault Reporting
Vending Machine
↓
Fault Service
↓
fault_events
↓
Maintenance Dashboard
Communication Patterns Used
Interaction TypePatternMachine → BackendREST (HTTP)Service → ServiceEvent-driven (Kafka)AggregationsAsync consumersQueriesCQRS (read vs write separation)
Why This Design Works (Interview Explanation)
High ingestion throughput → no sync dependencies
Event-driven → easy to add new consumers
Strong isolation → each service owns its DB
Eventually consistent aggregations
Easy reprocessing from raw events
“I keep ingestion, aggregation, and querying decoupled using event streams. That allows independent scaling and avoids cascading failures.”
Optional Enhancements (If Interviewer Pushes)
Kafka partitioned by
machine_idExactly-once aggregation using offsets + idempotent writes
OLAP store (ClickHouse / Druid) for analytics
gRPC for internal service calls


