problemmediumoodood-for-coffee-shop-order-managementood for coffee shop order managementoodforcoffeeshopordermanagementdesign-a-coffee-shop-systemdesign a coffee shop systemdesignacoffeeshopsystem

OOD - Coffee Shop Order Management System

MediumUpdated: Jan 1, 2026

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:

  1. Customers can browse the menu and place orders with customizations (size, milk type, add-ons)
  2. Cashiers process payments and create orders with unique order IDs (tokens)
  3. Orders are placed in a FIFO queue for baristas to process
  4. Baristas dequeue orders, view preparation instructions, and mark orders complete
  5. System notifies customers when their orders are ready (display board + verbal announcement)
  6. Support multiple payment methods (cash, card, mobile wallet)
  7. Track order status (PENDING, IN_PROGRESS, COMPLETED, PICKED_UP)
  8. Maintain order history for reporting and analytics
  9. Admin can add/remove menu items and adjust prices

Non-Functional Requirements:

  1. Concurrency: Multiple cashiers can create orders simultaneously without conflicts
  2. Thread Safety: Order queue must be thread-safe for concurrent producer-consumer access
  3. Reliability: Orders must not be lost if a barista worker crashes
  4. Performance: Order placement should complete within 2 seconds
  5. 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-safe BlockingQueue)

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: NotificationService maintains customer registry
  • Observers: Customer instances 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 synchronized blocks: 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

Comments