OOD - Coffee Shop Order Management System
1. Problem Statement
Design an object-oriented system for a coffee shop where customers place orders with a cashier, baristas prepare drinks from a queue, and customers are notified when orders are ready for pickup. The system must handle order placement, payment processing, queue management, drink preparation, and customer notifications while supporting concurrent operations and preventing order loss.
The coffee shop operates with three distinct roles: Customers pay and place orders receiving a token number, Cashiers process payments and enqueue orders, and Baristas dequeue orders, prepare beverages, and trigger completion notifications. The architecture must decouple order taking from order preparation to enable independent scaling of cashier stations and barista workstations based on demand.
2. System Requirements
Functional Requirements:
- Customers can browse the menu and place orders with customizations (size, milk type, add-ons)
- Cashiers process payments and create orders with unique order IDs (tokens)
- Orders are placed in a FIFO queue for baristas to process
- Baristas dequeue orders, view preparation instructions, and mark orders complete
- System notifies customers when their orders are ready (display board + verbal announcement)
- Support multiple payment methods (cash, card, mobile wallet)
- Track order status (PENDING, IN_PROGRESS, COMPLETED, PICKED_UP)
- Maintain order history for reporting and analytics
- Admin can add/remove menu items and adjust prices
Non-Functional Requirements:
- Concurrency: Multiple cashiers can create orders simultaneously without conflicts
- Thread Safety: Order queue must be thread-safe for concurrent producer-consumer access
- Reliability: Orders must not be lost if a barista worker crashes
- Performance: Order placement should complete within 2 seconds
- Scalability: Support adding more barista workers during peak hours
Assumptions:
- Customers always wait for order completion (no order cancellations)
- Order queue has unlimited capacity (can be modified for bounded queue)
- Each order gets a unique sequential ID serving as the customer token
- One barista processes one order at a time (serial processing per worker)
3. Use Case Diagram
Actors:
- Customer: Places orders, makes payments, receives notifications, picks up orders
- Cashier: Takes orders, processes payments, enqueues orders, issues tokens
- Barista: Dequeues orders, prepares drinks, marks orders complete
- Administrator: Manages menu items, configures pricing, views reports
Core Use Cases:
- Place Order & Pay
- Enqueue Order
- Dequeue & Prepare Order
- Notify Customer
- Pick Up Order
- Manage Menu
graph TB subgraph CoffeeShopSystem["Coffee Shop System"] UC1["Place Order & Pay"] UC2["Enqueue Order"] UC3["Dequeue Order"] UC4["Prepare Drink"] UC5["Notify Customer"] UC6["Pick Up Order"] UC7["Manage Menu"] UC8["View Reports"] end Customer([Customer]) Cashier([Cashier]) Barista([Barista]) Admin([Administrator]) System([System]) Customer --> UC1 Cashier --> UC1 Cashier --> UC2 Barista --> UC3 Barista --> UC4 System --> UC5 Customer --> UC6 Admin --> UC7 Admin --> UC8 UC1 -.->|includes| UC2 UC4 -.->|triggers| UC5 style Customer fill:#e1f5ff style Cashier fill:#fff3e0 style Barista fill:#f3e5f5 style Admin fill:#ffebee style System fill:#e8f5e9
4. Class Diagram
Core Classes:
- MenuItem: Represents an item on the menu (name, base price, category)
- Customization: Add-ons like extra shot, milk type, size (affects price)
- Order: Contains order ID, items with customizations, total price, status, timestamps
- OrderQueue: Thread-safe queue holding pending orders (BlockingQueue implementation)
- Customer: Places orders, receives token, gets notified
- Cashier: Creates orders, processes payments, enqueues to queue
- Barista: Worker thread that dequeues orders and prepares drinks
- Payment: Handles payment processing with amount and method
- NotificationService: Publishes order ready notifications (Observer pattern)
- OrderStatus: Enum (PENDING, IN_PROGRESS, COMPLETED, PICKED_UP)
classDiagram class MenuItem { -String id -String name -double basePrice -Category category +getPrice() double } class Customization { -String name -double additionalPrice } class Order { -String orderId -List~OrderItem~ items -double totalAmount -OrderStatus status -LocalDateTime createdAt -LocalDateTime completedAt +calculateTotal() double +updateStatus(OrderStatus) void } class OrderItem { -MenuItem menuItem -List~Customization~ customizations -int quantity +getItemTotal() double } class OrderQueue { -BlockingQueue~Order~ queue +enqueue(Order) void +dequeue() Order +size() int } class Customer { -String customerId -String name -String phoneNumber +placeOrder(List~OrderItem~) String +receiveNotification(String orderId) void } class Cashier { -String cashierId -OrderQueue orderQueue +takeOrder(Customer, List~OrderItem~) Order +processPayment(Payment) boolean +enqueueOrder(Order) void } class Barista { -String baristaId -OrderQueue orderQueue -NotificationService notifier +processNextOrder() void +prepareDrink(Order) void +markComplete(Order) void } class Payment { -double amount -PaymentMethod method -PaymentStatus status +process() boolean } class NotificationService { -List~Customer~ subscribers +notifyOrderReady(String orderId) void +subscribe(Customer) void } class OrderStatus { <<enumeration>> PENDING IN_PROGRESS COMPLETED PICKED_UP } class PaymentMethod { <<enumeration>> CASH CARD MOBILE_WALLET } Order "1" *-- "*" OrderItem OrderItem "*" --> "1" MenuItem OrderItem "*" --> "*" Customization Cashier --> OrderQueue : enqueues Barista --> OrderQueue : dequeues Cashier --> Payment : processes Barista --> NotificationService : notifies Customer --> NotificationService : subscribes Order --> OrderStatus Payment --> PaymentMethod
5. Activity Diagrams
Order Placement Workflow
graph TD A([Customer arrives]) --> B[Browse Menu] B --> C[Select items & customizations] C --> D[Cashier calculates total] D --> E[Customer provides payment] E --> F{Payment successful?} F -->|No| G[Request different payment method] G --> E F -->|Yes| H[Create Order with unique ID] H --> I[Enqueue Order] I --> J[Return token to Customer] J --> K([Customer waits for notification])
Order Preparation Workflow
graph TD A([Barista idle]) --> B[Poll OrderQueue] B --> C{Order available?} C -->|No| D[Wait/Sleep] D --> B C -->|Yes| E[Dequeue Order] E --> F[Update status to IN_PROGRESS] F --> G[Prepare drink following specs] G --> H[Quality check] H --> I[Update status to COMPLETED] I --> J[Place order in pickup area] J --> K[Notify customer via NotificationService] K --> L([Return to Poll]) L --> B
6. Java Implementation
Enums and Value Objects
public enum OrderStatus {
PENDING,
IN_PROGRESS,
COMPLETED,
PICKED_UP,
CANCELLED
}
public enum PaymentMethod {
CASH,
CREDIT_CARD,
DEBIT_CARD,
MOBILE_WALLET
}
public enum PaymentStatus {
PENDING,
AUTHORIZED,
COMPLETED,
FAILED,
REFUNDED
}
public enum Category {
ESPRESSO,
BREWED_COFFEE,
COLD_BREW,
TEA,
SPECIALTY
}
Menu and Items
public class MenuItem {
private final String id;
private final String name;
private final double basePrice;
private final Category category;
private final String description;
public MenuItem(String id, String name, double basePrice, Category category, String description) {
this.id = id;
this.name = name;
this.basePrice = basePrice;
this.category = category;
this.description = description;
}
public double getBasePrice() {
return basePrice;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
@Override
public String toString() {
return String.format("%s - $%.2f (%s)", name, basePrice, category);
}
}
public class Customization {
private final String name;
private final double additionalPrice;
public Customization(String name, double additionalPrice) {
this.name = name;
this.additionalPrice = additionalPrice;
}
public double getAdditionalPrice() {
return additionalPrice;
}
public String getName() {
return name;
}
}
public class OrderItem {
private final MenuItem menuItem;
private final List<Customization> customizations;
private final int quantity;
public OrderItem(MenuItem menuItem, int quantity) {
this.menuItem = menuItem;
this.quantity = quantity;
this.customizations = new ArrayList<>();
}
public void addCustomization(Customization customization) {
customizations.add(customization);
}
public double getItemTotal() {
double customizationTotal = customizations.stream()
.mapToDouble(Customization::getAdditionalPrice)
.sum();
return (menuItem.getBasePrice() + customizationTotal) * quantity;
}
public MenuItem getMenuItem() {
return menuItem;
}
public List<Customization> getCustomizations() {
return new ArrayList<>(customizations);
}
public int getQuantity() {
return quantity;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(quantity).append("x ").append(menuItem.getName());
if (!customizations.isEmpty()) {
sb.append(" (");
sb.append(customizations.stream()
.map(Customization::getName)
.collect(Collectors.joining(", ")));
sb.append(")");
}
return sb.toString();
}
}
Order and Payment
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
public class Order {
private static final AtomicLong orderCounter = new AtomicLong(0);
private final String orderId;
private final List<OrderItem> items;
private double totalAmount;
private OrderStatus status;
private final LocalDateTime createdAt;
private LocalDateTime completedAt;
private final String customerId;
public Order(String customerId, List<OrderItem> items) {
this.orderId = generateOrderId();
this.customerId = customerId;
this.items = new ArrayList<>(items);
this.status = OrderStatus.PENDING;
this.createdAt = LocalDateTime.now();
this.totalAmount = calculateTotal();
}
private static String generateOrderId() {
return String.format("ORD-%06d", orderCounter.incrementAndGet());
}
public double calculateTotal() {
return items.stream()
.mapToDouble(OrderItem::getItemTotal)
.sum();
}
public synchronized void updateStatus(OrderStatus newStatus) {
this.status = newStatus;
if (newStatus == OrderStatus.COMPLETED) {
this.completedAt = LocalDateTime.now();
}
}
public String getOrderId() {
return orderId;
}
public OrderStatus getStatus() {
return status;
}
public List<OrderItem> getItems() {
return new ArrayList<>(items);
}
public double getTotalAmount() {
return totalAmount;
}
public String getCustomerId() {
return customerId;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Order ").append(orderId).append(" [").append(status).append("]\n");
for (OrderItem item : items) {
sb.append(" - ").append(item).append("\n");
}
sb.append(String.format(" Total: $%.2f", totalAmount));
return sb.toString();
}
}
public class Payment {
private final String paymentId;
private final double amount;
private final PaymentMethod method;
private PaymentStatus status;
private final LocalDateTime timestamp;
public Payment(double amount, PaymentMethod method) {
this.paymentId = UUID.randomUUID().toString();
this.amount = amount;
this.method = method;
this.status = PaymentStatus.PENDING;
this.timestamp = LocalDateTime.now();
}
public boolean process() {
// Simulate payment processing
try {
Thread.sleep(100); // Simulate payment gateway latency
// In real system: call payment gateway API
this.status = PaymentStatus.COMPLETED;
return true;
} catch (InterruptedException e) {
this.status = PaymentStatus.FAILED;
Thread.currentThread().interrupt();
return false;
}
}
public PaymentStatus getStatus() {
return status;
}
public double getAmount() {
return amount;
}
}
Thread-Safe Order Queue
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class OrderQueue {
private final BlockingQueue<Order> queue;
public OrderQueue() {
this.queue = new LinkedBlockingQueue<>();
}
public OrderQueue(int capacity) {
this.queue = new LinkedBlockingQueue<>(capacity);
}
/**
* Enqueue order (non-blocking for unbounded queue)
*/
public void enqueue(Order order) throws InterruptedException {
queue.put(order);
System.out.println("Order " + order.getOrderId() + " added to queue. Queue size: " + queue.size());
}
/**
* Dequeue order (blocks if queue is empty)
*/
public Order dequeue() throws InterruptedException {
return queue.take();
}
public int size() {
return queue.size();
}
public boolean isEmpty() {
return queue.isEmpty();
}
}
Customer, Cashier, and Barista
public class Customer {
private final String customerId;
private final String name;
private final String phoneNumber;
public Customer(String customerId, String name, String phoneNumber) {
this.customerId = customerId;
this.name = name;
this.phoneNumber = phoneNumber;
}
public void receiveNotification(String orderId) {
System.out.println("📢 [" + name + "] Your order " + orderId + " is ready for pickup!");
}
public String getCustomerId() {
return customerId;
}
public String getName() {
return name;
}
public String getPhoneNumber() {
return phoneNumber;
}
}
public class Cashier {
private final String cashierId;
private final String name;
private final OrderQueue orderQueue;
public Cashier(String cashierId, String name, OrderQueue orderQueue) {
this.cashierId = cashierId;
this.name = name;
this.orderQueue = orderQueue;
}
public Order takeOrder(Customer customer, List<OrderItem> items) {
Order order = new Order(customer.getCustomerId(), items);
System.out.println("[Cashier " + name + "] Created " + order.getOrderId());
return order;
}
public boolean processPayment(Payment payment) {
System.out.println("[Cashier " + name + "] Processing payment of $" + payment.getAmount());
return payment.process();
}
public String enqueueOrder(Order order) throws InterruptedException {
orderQueue.enqueue(order);
System.out.println("[Cashier " + name + "] Order " + order.getOrderId() + " enqueued. Token: " + order.getOrderId());
return order.getOrderId(); // Token for customer
}
}
public class Barista implements Runnable {
private final String baristaId;
private final String name;
private final OrderQueue orderQueue;
private final NotificationService notificationService;
private volatile boolean running = true;
public Barista(String baristaId, String name, OrderQueue orderQueue, NotificationService notificationService) {
this.baristaId = baristaId;
this.name = name;
this.orderQueue = orderQueue;
this.notificationService = notificationService;
}
@Override
public void run() {
System.out.println("[Barista " + name + "] Started working");
while (running) {
try {
processNextOrder();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.println("[Barista " + name + "] Stopped working");
}
public void processNextOrder() throws InterruptedException {
Order order = orderQueue.dequeue();
System.out.println("[Barista " + name + "] Picked up " + order.getOrderId());
order.updateStatus(OrderStatus.IN_PROGRESS);
prepareDrink(order);
markComplete(order);
notificationService.notifyOrderReady(order);
}
private void prepareDrink(Order order) throws InterruptedException {
System.out.println("[Barista " + name + "] Preparing " + order.getOrderId());
for (OrderItem item : order.getItems()) {
System.out.println("[Barista " + name + "] - Making " + item);
Thread.sleep(2000); // Simulate drink preparation time
}
}
private void markComplete(Order order) {
order.updateStatus(OrderStatus.COMPLETED);
System.out.println("[Barista " + name + "] Completed " + order.getOrderId());
}
public void stop() {
running = false;
}
}
Notification Service (Observer Pattern)
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class NotificationService {
private final Map<String, Customer> customerRegistry;
public NotificationService() {
this.customerRegistry = new ConcurrentHashMap<>();
}
public void registerCustomer(Customer customer) {
customerRegistry.put(customer.getCustomerId(), customer);
}
public void notifyOrderReady(Order order) {
Customer customer = customerRegistry.get(order.getCustomerId());
if (customer != null) {
// Display board notification
displayOnBoard(order.getOrderId());
// Customer notification
customer.receiveNotification(order.getOrderId());
// Verbal announcement simulation
announceOrder(order.getOrderId());
}
}
private void displayOnBoard(String orderId) {
System.out.println("🖥️ [DISPLAY BOARD] Order " + orderId + " is ready!");
}
private void announceOrder(String orderId) {
System.out.println("🔊 [ANNOUNCEMENT] Order number " + orderId + ", your order is ready!");
}
}
Complete Demo Application
public class CoffeeShopDemo {
public static void main(String[] args) throws InterruptedException {
// Setup
OrderQueue orderQueue = new OrderQueue();
NotificationService notificationService = new NotificationService();
// Create menu items
MenuItem latte = new MenuItem("ITM001", "Latte", 4.50, Category.ESPRESSO, "Espresso with steamed milk");
MenuItem cappuccino = new MenuItem("ITM002", "Cappuccino", 4.00, Category.ESPRESSO, "Espresso with foam");
MenuItem coldBrew = new MenuItem("ITM003", "Cold Brew", 3.50, Category.COLD_BREW, "Smooth cold coffee");
// Customizations
Customization extraShot = new Customization("Extra Shot", 0.75);
Customization oatMilk = new Customization("Oat Milk", 0.50);
Customization vanilla = new Customization("Vanilla Syrup", 0.50);
// Create customers
Customer alice = new Customer("CUST001", "Alice", "555-0101");
Customer bob = new Customer("CUST002", "Bob", "555-0102");
Customer charlie = new Customer("CUST003", "Charlie", "555-0103");
notificationService.registerCustomer(alice);
notificationService.registerCustomer(bob);
notificationService.registerCustomer(charlie);
// Create cashier
Cashier cashier = new Cashier("CASH001", "Emma", orderQueue);
// Create and start baristas
Barista barista1 = new Barista("BAR001", "James", orderQueue, notificationService);
Barista barista2 = new Barista("BAR002", "Sofia", orderQueue, notificationService);
Thread baristaThread1 = new Thread(barista1);
Thread baristaThread2 = new Thread(barista2);
baristaThread1.start();
baristaThread2.start();
System.out.println("☕ Coffee Shop is now OPEN!\n");
// Scenario 1: Alice orders a latte with oat milk
System.out.println("=== Alice's Order ===");
OrderItem aliceItem = new OrderItem(latte, 1);
aliceItem.addCustomization(oatMilk);
List<OrderItem> aliceItems = List.of(aliceItem);
Order aliceOrder = cashier.takeOrder(alice, aliceItems);
Payment alicePayment = new Payment(aliceOrder.getTotalAmount(), PaymentMethod.CREDIT_CARD);
if (cashier.processPayment(alicePayment)) {
String token = cashier.enqueueOrder(aliceOrder);
System.out.println("✅ Alice received token: " + token + "\n");
}
Thread.sleep(1000);
// Scenario 2: Bob orders 2 cappuccinos with extra shot and vanilla
System.out.println("=== Bob's Order ===");
OrderItem bobItem = new OrderItem(cappuccino, 2);
bobItem.addCustomization(extraShot);
bobItem.addCustomization(vanilla);
List<OrderItem> bobItems = List.of(bobItem);
Order bobOrder = cashier.takeOrder(bob, bobItems);
Payment bobPayment = new Payment(bobOrder.getTotalAmount(), PaymentMethod.MOBILE_WALLET);
if (cashier.processPayment(bobPayment)) {
String token = cashier.enqueueOrder(bobOrder);
System.out.println("✅ Bob received token: " + token + "\n");
}
Thread.sleep(1000);
// Scenario 3: Charlie orders a cold brew
System.out.println("=== Charlie's Order ===");
OrderItem charlieItem = new OrderItem(coldBrew, 1);
List<OrderItem> charlieItems = List.of(charlieItem);
Order charlieOrder = cashier.takeOrder(charlie, charlieItems);
Payment charliePayment = new Payment(charlieOrder.getTotalAmount(), PaymentMethod.CASH);
if (cashier.processPayment(charliePayment)) {
String token = cashier.enqueueOrder(charlieOrder);
System.out.println("✅ Charlie received token: " + token + "\n");
}
// Wait for all orders to be completed
Thread.sleep(10000);
// Shutdown
barista1.stop();
barista2.stop();
baristaThread1.interrupt();
baristaThread2.interrupt();
System.out.println("\n☕ Coffee Shop is now CLOSED!");
}
}
7. Design Patterns Applied
1. Producer-Consumer Pattern
Intent: Decouple order creation (Cashiers) from order processing (Baristas) using a shared queue.
Implementation:
- Producers: Cashiers enqueue orders
- Consumers: Baristas dequeue and process orders
- Shared Resource:
OrderQueue(thread-safeBlockingQueue)
Benefits:
- Cashiers and baristas work independently at their own pace
- Easy to scale by adding more barista threads
- Built-in backpressure handling if queue is bounded
// Producer
cashier.enqueueOrder(order); // Non-blocking for unbounded queue
// Consumer
Order order = orderQueue.dequeue(); // Blocks until order available
2. Observer Pattern (Notification Service)
Intent: Notify customers when their orders are ready without tight coupling.
Implementation:
- Subject:
NotificationServicemaintains customer registry - Observers:
Customerinstances registered with service - Notification: Barista calls
notifyOrderReady()upon completion
Benefits:
- Customers don't need to poll for order status
- Support multiple notification channels (display, announcement, SMS)
- Easy to add new notification methods
// Subject notifies observers
notificationService.notifyOrderReady(order);
// Observer receives notification
customer.receiveNotification(orderId);
3. Builder Pattern (Order Creation)
Intent: Construct complex orders with many customizations step-by-step.
Potential Enhancement:
Order order = new OrderBuilder()
.forCustomer(customer)
.addItem(latte, 1)
.withCustomization(extraShot)
.withCustomization(oatMilk)
.addItem(croissant, 1)
.build();
4. Singleton Pattern (NotificationService)
Intent: Ensure single notification service instance manages all notifications.
Implementation: Could make NotificationService a singleton for centralized notification management.
5. Command Pattern (Future Enhancement)
Intent: Encapsulate order operations as commands for undo/logging.
Potential Use Cases:
- Order modification commands
- Cancellation commands
- Refund commands
6. State Pattern (Order Status)
Intent: Order behavior changes based on status (PENDING → IN_PROGRESS → COMPLETED).
Current Simple Implementation: Using enum; could be enhanced with State pattern for complex status transitions.
8. Key Design Decisions
Decision 1: BlockingQueue for Thread Safety
Rationale: Java's BlockingQueue provides thread-safe enqueue/dequeue operations without manual synchronization. The take() method blocks barista threads when queue is empty, eliminating busy-waiting.
Alternatives Considered:
- Manual synchronization with
synchronizedblocks: More error-prone ConcurrentLinkedQueue: Requires polling, wastes CPU- Database queue: Higher latency, unnecessary complexity
Decision 2: Immutable Order ID Generation
Rationale: Using AtomicLong counter ensures unique sequential order IDs in multi-threaded environment without locks.
Benefits:
- Human-readable tokens (ORD-000001, ORD-000002)
- No UUID collision concerns
- Easy sorting by creation order
Decision 3: Barista as Runnable
Rationale: Each barista runs in separate thread, continuously processing orders from queue. This models real coffee shop where multiple baristas work in parallel.
Benefits:
- True concurrency for drink preparation
- Independent worker lifecycle management
- Graceful shutdown via interrupt
Decision 4: Synchron ized Status Updates
Rationale: Order.updateStatus() is synchronized to prevent race conditions when multiple threads access same order.
Example Race Condition Prevented:
// Without synchronization, this could fail:
Thread 1: Reads status as PENDING
Thread 2: Reads status as PENDING
Thread 1: Sets status to IN_PROGRESS
Thread 2: Sets status to IN_PROGRESS (overwrites!)
Decision 5: Separate OrderItem from MenuItem
Rationale: MenuItem represents catalog items (immutable), while OrderItem adds quantity and customizations (order-specific).
Benefits:
- Menu items can be reused across orders
- Price calculations encapsulated in OrderItem
- Easy to add new customization types
Decision 6: Observer Pattern for Notifications
Rationale: Decouples order completion from customer notification mechanism.
Benefits:
- Baristas don't know about notification details
- Easy to add new notification channels (SMS, app push)
- Customers can be notified via multiple channels simultaneously
Decision 7: Simple Payment Model
Rationale: Payment processing is simulated (not integrated with real gateway) to focus on core order management workflow.
Production Enhancement:
// Real payment gateway integration
PaymentGateway gateway = new StripeGateway(apiKey);
PaymentResult result = gateway.charge(payment);
Decision 8: No Order Cancellation
Rationale: Simplified scope per problem assumptions. In production, would add:
- Cancellation window (e.g., within 1 minute of placement)
- Refund processing
- Queue removal mechanism
Decision 9: Unbounded Queue
Rationale: Simplified assumption that queue can grow indefinitely.
Production Enhancement:
// Bounded queue with capacity
OrderQueue queue = new OrderQueue(100);
// Handle queue full scenario
try {
queue.enqueue(order);
} catch (InterruptedException e) {
// Queue full: reject order or wait
}
Decision 10: In-Memory Storage
Rationale: Orders stored in memory for simplicity. Production system would use:
- Database for order persistence
- Cache (Redis) for active orders
- Message queue (RabbitMQ/Kafka) for durable order queue
Benefits of Current Approach:
- Fast prototyping
- Zero infrastructure dependencies
- Clear focus on OOD principles
Trade-offs:
- Orders lost on application restart
- No historical order analytics
- Limited by available RAM