Requirements & System Scope
Functional Specifications (In-Scope)
- Guest vs Logged-In Modes: Decouples anonymous session carts stored in-memory from user account carts stored in the user profile registry.
- Pluggable Conflict Resolution: Configurable merge policies (
KEEP_GUEST,KEEP_USER,SUM_QUANTITIES,PREFER_HIGHER_QTY). - Real-Time Inventory Validation: Verifies stock balances on merge, automatically downsizing or deleting out-of-stock items.
- User Alert Dispatcher: Captures stock discrepancy modifications to push notification alerts to logged-in users.
Out-of-Scope Boundaries
- Payment Integration Gateways: Excludes checkout transactions to focus strictly on session and inventory reconciliations.
- Persistent Cookie Store TTLs: Relies on simple React states to represent short-term cookie memory.
Class Diagram & Entity Relationships
Entity blueprint mapping session carts, user records, and stock checkers:
Loading...
- Cart & CartItem: Models generic collection bins with quantity and unit cost fields.
- InventoryService: Checks product counts before finalizing user balances.
- CartMergeService: Clears session logs after copying active balances.
Design Patterns & SOLID Principles
- Strategy Pattern: Encapsulates different conflict resolution merge strategies under a clean strategy policy selector.
- Facade Pattern: Exposes a single unified interface (
merge) that encapsulates the complexity of inventory checking, quantity adjustments, notifications generating, and session clearing. - Dependency Inversion Principle (DIP): Ensures
CartMergeServicedepends on abstract inventory validation lookups, decoupled from backend database details.
Core Execution Workflows
Guest Merge & Stock Check Execution Workflow
- Trigger Authentication Login:
- Load current
GuestCartassociated with anonymoussessionId. - Load target
UserCartassociated with authenticateduserId.
- Load current
- Merge & Conflict Resolution:
- For each product in guest cart: if it also exists in user cart, resolve quantities using policy (e.g.,
max(GuestQty, UserQty)). - If the product only exists in guest cart, add to target list.
- For each product in guest cart: if it also exists in user cart, resolve quantities using policy (e.g.,
- Real-time Stock Constraints:
- Query available stock. If stock is 0, delete item from cart and push an alert: “Removed item (Out of Stock)”.
- If stock is less than resolved quantity, downscale quantity to matches available stock and push warning alert.
- Clean up session cart and save merged products to persistent user database.
Concurrency & Thread Safety Strategy
Securing carts under heavy simultaneous merges (e.g. user logs in on both phone and browser at once):
- Concurrent Hash Maps: Prevents deadlocks during asynchronous session lookups.
- Pessimistic Inventory Locking: Locks inventory records when checking stock balances to prevent double-allocating limited products during simultaneous checkouts.
Complete Clean Code Blueprint
Production reference implementations demonstrating conflict solvers, inventory integrations, and clean cleanup mechanisms in Java and Python:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
enum MergePolicy {
KEEP_GUEST,
KEEP_USER,
SUM_QUANTITIES,
PREFER_HIGHER_QTY
}
class CartItem {
private final String productId;
private int quantity;
private final double unitPrice;
private final long addedAt;
public CartItem(String productId, int quantity, double unitPrice) {
this.productId = productId;
this.quantity = quantity;
this.unitPrice = unitPrice;
this.addedAt = System.currentTimeMillis();
}
public String getProductId() { return productId; }
public int getQuantity() { return quantity; }
public double getUnitPrice() { return unitPrice; }
public long getAddedAt() { return addedAt; }
public void setQuantity(int quantity) { this.quantity = quantity; }
}
class Cart {
private final String cartId; // sessionId or userId
private final Map<String, CartItem> items = new ConcurrentHashMap<>();
private long lastUpdated;
public Cart(String cartId) {
this.cartId = cartId;
this.lastUpdated = System.currentTimeMillis();
}
public String getCartId() { return cartId; }
public Map<String, CartItem> getItems() { return items; }
public long getLastUpdated() { return lastUpdated; }
public void addItem(String productId, int qty, double price) {
items.merge(productId, new CartItem(productId, qty, price), (oldVal, newVal) -> {
oldVal.setQuantity(oldVal.getQuantity() + qty);
return oldVal;
});
lastUpdated = System.currentTimeMillis();
}
public void clear() {
items.clear();
lastUpdated = System.currentTimeMillis();
}
}
class InventoryService {
private final Map<String, Integer> stockRegistry = new ConcurrentHashMap<>();
public void setStock(String productId, int qty) {
stockRegistry.put(productId, qty);
}
public int getAvailableStock(String productId) {
return stockRegistry.getOrDefault(productId, 0);
}
}
class MergeResult {
private final Cart mergedCart;
private final List<String> notifications;
public MergeResult(Cart mergedCart, List<String> notifications) {
this.mergedCart = mergedCart;
this.notifications = notifications;
}
public Cart getMergedCart() { return mergedCart; }
public List<String> getNotifications() { return notifications; }
}
class CartMergeService {
private final Map<String, Cart> guestCarts = new ConcurrentHashMap<>();
private final Map<String, Cart> userCarts = new ConcurrentHashMap<>();
private final InventoryService inventoryService;
public CartMergeService(InventoryService inventoryService) {
this.inventoryService = inventoryService;
}
public Cart getGuestCart(String sessionId) {
return guestCarts.computeIfAbsent(sessionId, Cart::new);
}
public Cart getUserCart(String userId) {
return userCarts.computeIfAbsent(userId, Cart::new);
}
public MergeResult merge(String sessionId, String userId, MergePolicy policy) {
Cart guestCart = guestCarts.get(sessionId);
Cart userCart = getUserCart(userId);
List<String> notifications = new ArrayList<>();
if (guestCart == null || guestCart.getItems().isEmpty()) {
return new MergeResult(userCart, notifications);
}
// Apply merge logic
for (CartItem guestItem : guestCart.getItems().values()) {
String prodId = guestItem.getProductId();
CartItem userItem = userCart.getItems().get(prodId);
int targetQty = 0;
if (userItem == null) {
targetQty = guestItem.getQuantity();
} else {
switch (policy) {
case KEEP_GUEST:
targetQty = guestItem.getQuantity();
break;
case KEEP_USER:
targetQty = userItem.getQuantity();
break;
case SUM_QUANTITIES:
targetQty = userItem.getQuantity() + guestItem.getQuantity();
break;
case PREFER_HIGHER_QTY:
targetQty = Math.max(userItem.getQuantity(), guestItem.getQuantity());
break;
}
}
// Inventory Validation Check
int availableStock = inventoryService.getAvailableStock(prodId);
if (availableStock <= 0) {
notifications.add("Removed product: " + prodId + " (Out of Stock)");
userCart.getItems().remove(prodId);
} else if (targetQty > availableStock) {
notifications.add("Reduced qty of: " + prodId + " from " + targetQty + " to " + availableStock + " due to limited stock.");
if (userItem != null) {
userItem.setQuantity(availableStock);
} else {
userCart.addItem(prodId, availableStock, guestItem.getUnitPrice());
}
} else {
if (userItem != null) {
userItem.setQuantity(targetQty);
} else {
userCart.addItem(prodId, targetQty, guestItem.getUnitPrice());
}
}
}
// Clean up Session Guest Cart
guestCarts.remove(sessionId);
return new MergeResult(userCart, notifications);
}
}
public class Main {
public static void main(String[] args) {
System.out.println("=== JAVA SHOPPING CART MERGE SIMULATION ===");
InventoryService inventory = new InventoryService();
inventory.setStock("laptop", 2);
inventory.setStock("headphones", 0);
inventory.setStock("keyboard", 5);
CartMergeService service = new CartMergeService(inventory);
// Guest session adding items
String sessionId = "guest-session-999";
Cart guestCart = service.getGuestCart(sessionId);
guestCart.addItem("laptop", 3, 1200.0);
guestCart.addItem("headphones", 1, 250.0);
// Authenticated user cart has some existing items
String userId = "user-alice";
Cart userCart = service.getUserCart(userId);
userCart.addItem("laptop", 1, 1200.0);
userCart.addItem("keyboard", 1, 150.0);
System.out.println("Guest Cart Qty of laptop: " + guestCart.getItems().get("laptop").getQuantity());
System.out.println("User Cart Qty of laptop: " + userCart.getItems().get("laptop").getQuantity());
// Perform merge
System.out.println("\nLogging in guest to merge carts with PREFER_HIGHER_QTY policy...");
MergeResult result = service.merge(sessionId, userId, MergePolicy.PREFER_HIGHER_QTY);
System.out.println("Merge complete. Notifications:");
for (String note : result.getNotifications()) {
System.out.println(" - " + note);
}
System.out.println("\nFinal Merged User Cart Items:");
for (CartItem item : result.getMergedCart().getItems().values()) {
System.out.println("Product: " + item.getProductId() + ", Qty: " + item.getQuantity() + ", Price: $" + item.getUnitPrice());
}
System.out.println("=== END OF JAVA SIMULATION ===");
}
}
Interactive Simulator
❖ Guest & Logged-in Cart Merger Simulator
Configure stock balances, choose dynamic merge policies, simulate user logins, and resolve out-of-stock discrepancies on the fly.
🛒 Anonymous Guest Cart (Session)Active Session
Pro LaptopQty: 3
Noise Cancelling HeadphonesQty: 1
Wireless MouseQty: 2
👤 Authenticated User Cart (Alice)Persistent Store
Pro LaptopQty: 1
Mechanical KeyboardQty: 1
Merge Trigger Panel
Inventory Registry Status
Configure stock levels on the fly to see how the merge service prevents double-booking or out-of-stock errors.
Pro Laptop
Cost: $1200
2
Noise Cancelling Headphones
Cost: $250
0
Mechanical Keyboard
Cost: $150
5
Wireless Mouse
Cost: $80
10
Add Items to Carts:
Pro Laptop
Noise Cancelling Headphones
Mechanical Keyboard
Wireless Mouse
System Reconcile Notifications
Trigger login to run reconciliation.
Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...