Design a Vending Machine
Design a vending machine system with state machine modeling, inventory management, payment handling, and remote monitoring.
Problem Statement
Design a vending machine system that manages inventory, processes multiple payment methods, dispenses products via a state machine, and supports remote monitoring across a fleet of machines. The design should cover both the embedded software on the machine and the cloud backend for fleet management.
Requirements
Functional
- Accept payment via coins, bills, credit card, and mobile pay (NFC)
- Display available products with prices; gray out sold-out items
- Dispense selected product and return correct change
- Remote inventory monitoring and restocking alerts for operators
Non-Functional
- Reliability: Must dispense correctly or refund -- no lost money
- Offline resilience: Machine operates for cash transactions even if network is down
- Latency: Product dispensed within 3 seconds of confirmed payment
- Scale: Fleet of 50,000 machines reporting to a central backend
Core Architecture
-
State Machine Controller -- The core on-machine logic. States: IDLE -> ACCEPTING_PAYMENT -> PRODUCT_SELECTED -> DISPENSING -> RETURNING_CHANGE -> IDLE. Transitions are triggered by hardware events (coin inserted, button pressed, product dropped). Invalid transitions (e.g., selecting product before payment) are rejected.
-
Payment Processor -- On-machine module that tracks inserted amount for cash, or communicates with card terminal for electronic payments. Validates that the accumulated amount >= product price before allowing dispensing. For electronic payments, calls the gateway with a timeout and rolls back on failure.
-
Inventory Manager -- Tracks quantity per slot. Decrements on successful dispense. When a product reaches a threshold (e.g., 2 remaining), sends a restocking alert to the cloud backend. Persists inventory state to local flash storage to survive power loss.
- Fleet Management Backend -- Cloud service receiving heartbeats and inventory telemetry from all machines via MQTT. Provides a dashboard for operators showing real-time stock levels, revenue, and machine health. Triggers restocking routes and technician dispatch.
Database Choice
On-machine: Embedded SQLite for local inventory, transaction logs, and state recovery after power loss. Cloud backend: PostgreSQL for machine registry, operator accounts, and restocking schedules. TimescaleDB (Postgres extension) for time-series telemetry (temperature, sales events, error logs). Redis for real-time fleet dashboards aggregating machine status.
Key API Endpoints
POST /api/v1/machines/\{machine_id\}/telemetry
-> Body: \{ inventory: [\{ slot: "A1", product: "Cola", qty: 3 \}], revenue_today: 142.50, temp_c: 4.2 \}
GET /api/v1/machines/\{machine_id\}/status
-> Returns: \{ online: true, last_heartbeat: "...", low_stock_slots: ["B3", "C1"] \}
POST /api/v1/machines/\{machine_id\}/restock
-> Body: \{ slots: [\{ slot: "A1", qty_added: 10 \}], technician_id: "T-42" \}
Scaling Insight
Use MQTT with a fan-in broker (e.g., EMQX) for machine-to-cloud communication instead of HTTP polling. Each machine maintains a persistent MQTT connection, sending telemetry every 60 seconds and receiving config pushes (price changes, new products) instantly. MQTT handles 50K concurrent connections easily on a single broker and uses <1 KB per heartbeat, keeping cellular data costs low for machines on mobile networks.
Key Tradeoffs
| Decision | Option A | Option B | Chosen |
|---|---|---|---|
| Communication | HTTP polling | MQTT persistent connection | MQTT -- lower bandwidth, real-time push capability, better for cellular |
| Payment failure | Block until gateway responds | Timeout and refund after 10s | Timeout -- prevents machine from locking up on network issues |
| Inventory sync | Real-time per transaction | Batch every 5 minutes | Batch -- reduces cellular costs, acceptable delay for restocking decisions |
Practical Implementation for .NET Developers
In a .NET application, you would typically implement this pattern using the following approach:
ASP.NET Core setup: Create a service class that encapsulates the logic, register it with dependency injection, and inject it into your controllers or minimal API endpoints. The built-in DI container handles lifecycle management.
Entity Framework Core: For database interactions, EF Core provides the ORM layer. Use migrations for schema management and raw SQL for performance-critical queries. Consider Dapper for read-heavy paths where EF Core's overhead matters.
Azure integration: If deploying to Azure, leverage managed services — Azure Cache for Redis, Azure SQL, Azure Service Bus, Azure Cosmos DB. These eliminate operational overhead and provide built-in monitoring through Application Insights.
Testing: Use xUnit with Testcontainers for integration tests that spin up real databases in Docker. Mock external dependencies with NSubstitute. The WebApplicationFactory class lets you test your entire HTTP pipeline in-process.
Monitoring: Add Application Insights telemetry to track request latency, dependency calls, and custom metrics. Use structured logging with Serilog to make production debugging possible:
Log.Information("Processing order {OrderId} for {CustomerId}", orderId, customerId);
This gives you searchable, structured logs in Azure Monitor or Seq.
System-Specific Clarifying Questions
Before designing Vending Machine, ask questions specific to THIS system:
- Who are the primary users? Understanding the user base shapes every technical decision — consumer apps have different requirements than enterprise B2B systems.
- What is the read-to-write ratio? This determines whether you optimize for fast reads (caching, denormalization) or fast writes (write-ahead logs, async processing).
- What is the geographic distribution? Users in one country vs. global users fundamentally changes your data replication and CDN strategy.
- What is the acceptable latency? Some features need sub-100ms responses, others can tolerate seconds. This determines your caching and architecture strategy.
- What is the consistency requirement? Some data (payments, inventory) needs strong consistency. Other data (social feeds, recommendations) can be eventually consistent.
Architecture Deep Dive
The architecture for Vending Machine should be designed around the specific access patterns of the system. Do not apply generic templates — every system has unique hotspots, bottlenecks, and scaling challenges.
Write Path: How does data enter the system? Is it bursty (event-driven, flash sales) or steady (sensor data, logs)? Bursty writes need queuing and backpressure. Steady writes can go directly to the database.
Read Path: How is data consumed? Is it fan-out (one write, many reads like social feeds) or point lookups (one read for specific data like user profiles)? Fan-out reads benefit from pre-computation and caching. Point lookups benefit from efficient indexing.
Hot Spots: Where are the bottlenecks? For Vending Machine, identify the component that will fail first under load and design mitigation strategies: caching, sharding, rate limiting, or async processing.