order service
I’ll provide the code for Stories 1-5, building incrementally as described in the epic.
Story 1: Basic Project Setup and Order Model
// Order.java
import java.util.List;
import java.util.ArrayList;
public class Order {
private final long orderId;
private final long userId;
private final List<String> items;
private final boolean isPremiumUser;
private final double totalAmount;
public Order(long orderId, long userId, List<String> items, boolean isPremiumUser, double totalAmount) {
this.orderId = orderId;
this.userId = userId;
this.items = new ArrayList<>(items);
this.isPremiumUser = isPremiumUser;
this.totalAmount = totalAmount;
}
// Getters
public long getOrderId() { return orderId; }
public long getUserId() { return userId; }
public List<String> getItems() { return new ArrayList<>(items); }
public boolean isPremiumUser() { return isPremiumUser; }
public double getTotalAmount() { return totalAmount; }
@Override
public String toString() {
return String.format("Order{id=%d, userId=%d, items=%s, premium=%b, amount=%.2f}",
orderId, userId, items, isPremiumUser, totalAmount);
}
}
// Main.java
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// Create sample orders
Order order1 = new Order(1001, 101, Arrays.asList("Laptop", "Mouse"), true, 1299.99);
Order order2 = new Order(1002, 102, Arrays.asList("Keyboard", "Monitor"), false, 450.00);
Order order3 = new Order(1003, 103, Arrays.asList("iPhone", "AirPods"), true, 1199.00);
// Print orders
System.out.println("Sample Orders Created:");
System.out.println(order1);
System.out.println(order2);
System.out.println(order3);
}
}
Story 2: Simple Single-Threaded Order Processing
// OrderProcessor.java
import java.util.HashMap;
import java.util.Map;
public class OrderProcessor {
private final Map<String, Integer> inventory;
public OrderProcessor() {
// Initialize inventory with sample items
inventory = new HashMap<>();
inventory.put("Laptop", 50);
inventory.put("Mouse", 200);
inventory.put("Keyboard", 150);
inventory.put("Monitor", 75);
inventory.put("iPhone", 100);
inventory.put("AirPods", 120);
}
public void processOrder(Order order) {
System.out.println("Processing order: " + order.getOrderId());
// Step 1: Validate order
if (!validateOrder(order)) {
System.out.println("Order validation failed for: " + order.getOrderId());
return;
}
// Step 2: Check and update inventory
boolean inventoryUpdated = checkAndUpdateInventory(order);
if (!inventoryUpdated) {
System.out.println("Insufficient inventory for order: " + order.getOrderId());
return;
}
// Step 3: Process payment (simulated)
simulatePaymentProcessing();
// Step 4: Log success
System.out.println("Successfully processed order: " + order.getOrderId());
System.out.println("Current inventory: " + inventory);
}
private boolean validateOrder(Order order) {
// Simple validation
return order.getTotalAmount() > 0 && !order.getItems().isEmpty();
}
private boolean checkAndUpdateInventory(Order order) {
// First check if all items are available
for (String item : order.getItems()) {
Integer quantity = inventory.get(item);
if (quantity == null || quantity < 1) {
return false;
}
}
// Update inventory
for (String item : order.getItems()) {
inventory.put(item, inventory.get(item) - 1);
}
return true;
}
private void simulatePaymentProcessing() {
try {
Thread.sleep(100); // Simulate payment processing time
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Updated Main.java for Story 2
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// Create order processor
OrderProcessor processor = new OrderProcessor();
// Create sample orders
List<Order> orders = new ArrayList<>();
orders.add(new Order(1001, 101, Arrays.asList("Laptop", "Mouse"), true, 1299.99));
orders.add(new Order(1002, 102, Arrays.asList("Keyboard", "Monitor"), false, 450.00));
orders.add(new Order(1003, 103, Arrays.asList("iPhone", "AirPods"), true, 1199.00));
orders.add(new Order(1004, 104, Arrays.asList("Laptop", "Keyboard"), false, 1199.00));
orders.add(new Order(1005, 105, Arrays.asList("Mouse", "Monitor"), true, 350.00));
// Process orders sequentially
long startTime = System.currentTimeMillis();
for (Order order : orders) {
processor.processOrder(order);
System.out.println("---");
}
long endTime = System.currentTimeMillis();
System.out.println("Total processing time: " + (endTime - startTime) + " ms");
}
}
Story 3: Introduce Thread Pool for Concurrent Processing
// Updated Main.java for Story 3
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
// Create order processor
OrderProcessor processor = new OrderProcessor();
// Create thread pool
ExecutorService executorService = Executors.newFixedThreadPool(4);
// Create sample orders (more orders to see concurrency)
List<Order> orders = createSampleOrders();
// Process orders concurrently
long startTime = System.currentTimeMillis();
for (Order order : orders) {
executorService.submit(() -> {
System.out.println("Thread " + Thread.currentThread().getName() +
" processing order " + order.getOrderId());
processor.processOrder(order);
});
}
// Shutdown executor
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.SECONDS);
long endTime = System.currentTimeMillis();
System.out.println("\nTotal processing time with threads: " + (endTime - startTime) + " ms");
System.out.println("Note: Inventory may have race conditions!");
}
private static List<Order> createSampleOrders() {
List<Order> orders = new ArrayList<>();
// Create 20 orders with overlapping items to demonstrate race conditions
for (int i = 1; i <= 20; i++) {
boolean isPremium = i % 3 == 0;
orders.add(new Order(1000 + i, 100 + i,
Arrays.asList("Laptop", "Mouse"), isPremium, 1299.99));
}
return orders;
}
}
Story 4: Add Synchronization for Thread-Safe Inventory Updates
// Updated OrderProcessor.java for Story 4
import java.util.HashMap;
import java.util.Map;
public class OrderProcessor {
private final Map<String, Integer> inventory;
private final Object inventoryLock = new Object(); // Lock object
public OrderProcessor() {
inventory = new HashMap<>();
inventory.put("Laptop", 50);
inventory.put("Mouse", 200);
inventory.put("Keyboard", 150);
inventory.put("Monitor", 75);
inventory.put("iPhone", 100);
inventory.put("AirPods", 120);
}
public void processOrder(Order order) {
System.out.println("Thread " + Thread.currentThread().getName() +
" processing order: " + order.getOrderId());
if (!validateOrder(order)) {
System.out.println("Order validation failed for: " + order.getOrderId());
return;
}
boolean inventoryUpdated = checkAndUpdateInventory(order);
if (!inventoryUpdated) {
System.out.println("Insufficient inventory for order: " + order.getOrderId());
return;
}
simulatePaymentProcessing();
System.out.println("Successfully processed order: " + order.getOrderId());
// Print inventory snapshot
synchronized (inventoryLock) {
System.out.println("Inventory snapshot: " + inventory);
}
}
private boolean validateOrder(Order order) {
return order.getTotalAmount() > 0 && !order.getItems().isEmpty();
}
private boolean checkAndUpdateInventory(Order order) {
synchronized (inventoryLock) {
// Check if all items are available
for (String item : order.getItems()) {
Integer quantity = inventory.get(item);
if (quantity == null || quantity < 1) {
return false;
}
}
// Update inventory atomically
for (String item : order.getItems()) {
inventory.put(item, inventory.get(item) - 1);
}
return true;
}
}
private void simulatePaymentProcessing() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// Test class to verify thread safety
public class ThreadSafetyTest {
public static void main(String[] args) throws InterruptedException {
OrderProcessor processor = new OrderProcessor();
ExecutorService executorService = Executors.newFixedThreadPool(10);
// Create 100 orders for the same item to test race conditions
List<Order> orders = new ArrayList<>();
for (int i = 1; i <= 100; i++) {
orders.add(new Order(1000 + i, 100 + i,
Arrays.asList("Laptop"), false, 1299.99));
}
for (Order order : orders) {
executorService.submit(() -> processor.processOrder(order));
}
executorService.shutdown();
executorService.awaitTermination(30, TimeUnit.SECONDS);
System.out.println("\nTest completed. Check if exactly 50 orders were processed successfully.");
}
}
Story 5: Switch to Concurrent Data Structures
// Updated OrderProcessor.java for Story 5
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.Map;
public class OrderProcessor {
private final ConcurrentHashMap<String, AtomicInteger> inventory;
public OrderProcessor() {
inventory = new ConcurrentHashMap<>();
inventory.put("Laptop", new AtomicInteger(50));
inventory.put("Mouse", new AtomicInteger(200));
inventory.put("Keyboard", new AtomicInteger(150));
inventory.put("Monitor", new AtomicInteger(75));
inventory.put("iPhone", new AtomicInteger(100));
inventory.put("AirPods", new AtomicInteger(120));
}
public void processOrder(Order order) {
System.out.println("Thread " + Thread.currentThread().getName() +
" processing order: " + order.getOrderId());
if (!validateOrder(order)) {
System.out.println("Order validation failed for: " + order.getOrderId());
return;
}
boolean inventoryUpdated = checkAndUpdateInventory(order);
if (!inventoryUpdated) {
System.out.println("Insufficient inventory for order: " + order.getOrderId());
return;
}
simulatePaymentProcessing();
System.out.println("Successfully processed order: " + order.getOrderId() +
" by " + Thread.currentThread().getName());
printInventorySnapshot();
}
private boolean validateOrder(Order order) {
return order.getTotalAmount() > 0 && !order.getItems().isEmpty();
}
private boolean checkAndUpdateInventory(Order order) {
// First, reserve all items atomically
Map<String, Integer> itemsToReserve = new HashMap<>();
for (String item : order.getItems()) {
itemsToReserve.merge(item, 1, Integer::sum);
}
// Try to reserve all items
Map<String, Integer> reserved = new HashMap<>();
for (Map.Entry<String, Integer> entry : itemsToReserve.entrySet()) {
String item = entry.getKey();
int needed = entry.getValue();
AtomicInteger stock = inventory.get(item);
if (stock == null) {
rollbackReservations(reserved);
return false;
}
// Try to reserve
int currentStock = stock.get();
if (currentStock < needed) {
rollbackReservations(reserved);
return false;
}
// Use compareAndSet for atomic update
while (true) {
int current = stock.get();
if (current < needed) {
rollbackReservations(reserved);
return false;
}
if (stock.compareAndSet(current, current - needed)) {
reserved.put(item, needed);
break;
}
// Retry if another thread modified the value
}
}
return true;
}
private void rollbackReservations(Map<String, Integer> reserved) {
for (Map.Entry<String, Integer> entry : reserved.entrySet()) {
inventory.get(entry.getKey()).addAndGet(entry.getValue());
}
}
private void simulatePaymentProcessing() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void printInventorySnapshot() {
StringBuilder sb = new StringBuilder("Inventory: {");
inventory.forEach((item, quantity) ->
sb.append(item).append("=").append(quantity.get()).append(", "));
if (sb.length() > 12) {
sb.setLength(sb.length() - 2);
}
sb.append("}");
System.out.println(sb.toString());
}
}
// Performance comparison test
public class PerformanceTest {
public static void main(String[] args) throws InterruptedException {
OrderProcessor processor = new OrderProcessor();
ExecutorService executorService = Executors.newFixedThreadPool(20);
// Create 1000 orders
List<Order> orders = new ArrayList<>();
for (int i = 1; i <= 1000; i++) {
List<String> items = Arrays.asList(
i % 2 == 0 ? "Laptop" : "iPhone",
i % 3 == 0 ? "Mouse" : "Keyboard"
);
orders.add(new Order(1000 + i, 100 + i, items, i % 5 == 0, 999.99));
}
long startTime = System.currentTimeMillis();
for (Order order : orders) {
executorService.submit(() -> processor.processOrder(order));
}
executorService.shutdown();
executorService.awaitTermination(60, TimeUnit.SECONDS);
long endTime = System.currentTimeMillis();
System.out.println("\nProcessed 1000 orders in: " + (endTime - startTime) + " ms");
System.out.println("With ConcurrentHashMap and AtomicInteger - no locks needed!");
}
}
These implementations show the progression from:
- Basic single-threaded model
- Introduction of concurrency (with race conditions)
- Thread safety with synchronization
- High-performance concurrent data structures
Each story builds on the previous one, demonstrating key multi-threading concepts in a practical e-commerce context.