Below is a comprehensive Low-Level Design (LLD) for an E-commerce Website in Java. In this design, we model the major entities and their relationships, manage products and inventory, process orders and payments, and provide a seamless user experience through secure authentication and notification updates. In addition to the Singleton, Factory, and Strategy patterns, we also implement the Observer pattern to notify customers of order status updates (for example, order placement, cancellation, or shipment).
1. Overview
Entities and Relationships
Product:
Represents an item in the catalog with details such as price, description, and available stock.
Customer:
Represents a shopper with authentication details (email and password) and a list of orders.
Order & OrderItem:
An Order consists of multiple OrderItems; each item is linked to a product and holds quantity and pricing details.
Payment:
Represents payment details for an order.
Product Management
ProductCatalog (Singleton):
Manages the product inventory and catalog.
OrderManager (Singleton & Factory):
Creates orders from a list of product IDs and quantities, updating the inventory accordingly.
PaymentService (Singleton) & PaymentStrategy (Strategy Pattern):
Processes payments via interchangeable strategies (e.g., Credit Card or PayPal).
Order Notifications (Observer Pattern)
OrderObserver Interface:
Defines a method for observers to receive order status updates.
Concrete Observer (EmailOrderObserver):
Sends (simulated) email notifications when an order is placed, cancelled, or updated.
OrderManager (Subject):
Notifies registered observers about order events.
2. Detailed Java Code
import java.util.*;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
// --------------------- ENUMS ---------------------
// Enum representing order statuses.
enum OrderStatus {
PLACED,
CANCELLED,
SHIPPED,
DELIVERED
}
// --------------------- ENTITIES ---------------------
// Product entity represents an item in the catalog.
class Product {
private String productId;
private String name;
private String description;
private double price;
private int stock; // Inventory count
public Product(String productId, String name, String description, double price, int stock) {
this.productId = productId;
this.name = name;
this.description = description;
this.price = price;
this.stock = stock;
}
public String getProductId() { return productId; }
public String getName() { return name; }
public String getDescription() { return description; }
public double getPrice() { return price; }
public synchronized int getStock() { return stock; }
// Reduce stock when an order is placed.
public synchronized boolean reduceStock(int quantity) {
if (stock >= quantity) {
stock -= quantity;
return true;
}
return false;
}
// Increase stock (e.g., on order cancellation).
public synchronized void increaseStock(int quantity) {
stock += quantity;
}
}
// Customer entity represents a shopper.
class Customer {
private String customerId;
private String name;
private String email;
private String password; // In real-world, stored securely.
private List<Order> orders; // Customer's order history.
public Customer(String customerId, String name, String email, String password) {
this.customerId = customerId;
this.name = name;
this.email = email;
this.password = password;
this.orders = new ArrayList<>();
}
public String getCustomerId() { return customerId; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public List<Order> getOrders() { return orders; }
public void addOrder(Order order) {
orders.add(order);
}
}
// OrderItem represents an individual item within an order.
class OrderItem {
private Product product;
private int quantity;
private double unitPrice;
public OrderItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
this.unitPrice = product.getPrice();
}
public Product getProduct() { return product; }
public int getQuantity() { return quantity; }
public double getUnitPrice() { return unitPrice; }
public double getTotalPrice() { return unitPrice * quantity; }
}
// Order entity represents a customer order.
class Order {
private String orderId;
private Customer customer;
private List<OrderItem> items;
private OrderStatus status;
private LocalDateTime orderTime;
private double totalAmount;
public Order(String orderId, Customer customer) {
this.orderId = orderId;
this.customer = customer;
this.items = new ArrayList<>();
this.status = OrderStatus.PLACED;
this.orderTime = LocalDateTime.now();
this.totalAmount = 0.0;
}
public String getOrderId() { return orderId; }
public Customer getCustomer() { return customer; }
public List<OrderItem> getItems() { return items; }
public OrderStatus getStatus() { return status; }
public LocalDateTime getOrderTime() { return orderTime; }
public double getTotalAmount() { return totalAmount; }
// Add an order item and update the order total.
public void addItem(OrderItem item) {
items.add(item);
totalAmount += item.getTotalPrice();
}
// Cancel the order.
public void cancelOrder() {
status = OrderStatus.CANCELLED;
}
}
// Payment entity representing payment details for an order.
class Payment {
private String paymentId;
private double amount;
private String paymentMethod; // E.g., CreditCard, PayPal.
private boolean isSuccessful;
public Payment(String paymentId, double amount, String paymentMethod) {
this.paymentId = paymentId;
this.amount = amount;
this.paymentMethod = paymentMethod;
this.isSuccessful = false;
}
public String getPaymentId() { return paymentId; }
public double getAmount() { return amount; }
public String getPaymentMethod() { return paymentMethod; }
public boolean isSuccessful() { return isSuccessful; }
public void markAsSuccessful() { isSuccessful = true; }
}
// --------------------- PRODUCT CATALOG ---------------------
/*
* Singleton Pattern: ProductCatalog centralizes the product inventory and catalog.
*/
class ProductCatalog {
private static ProductCatalog instance;
private Map<String, Product> products;
private ProductCatalog() {
products = new ConcurrentHashMap<>();
// Pre-load some dummy products.
addProduct(new Product("P001", "Smartphone", "Latest model smartphone", 699.99, 50));
addProduct(new Product("P002", "Laptop", "High performance laptop", 1299.99, 30));
addProduct(new Product("P003", "Headphones", "Noise-cancelling headphones", 199.99, 100));
}
public static synchronized ProductCatalog getInstance() {
if (instance == null) {
instance = new ProductCatalog();
}
return instance;
}
public void addProduct(Product product) {
products.put(product.getProductId(), product);
}
public Product getProductById(String productId) {
return products.get(productId);
}
public Collection<Product> getAllProducts() {
return products.values();
}
}
// --------------------- AUTHENTICATION SERVICE ---------------------
/*
* Singleton Pattern: AuthenticationService handles customer authentication.
*/
class AuthenticationService {
private static AuthenticationService instance;
// In-memory storage for customers (for demonstration).
private Map<String, Customer> customerDB;
private AuthenticationService() {
customerDB = new ConcurrentHashMap<>();
// Pre-load dummy customers. (Passwords stored in plain text for demo only.)
customerDB.put("user1@example.com", new Customer("C001", "Alice Johnson", "user1@example.com", "pass123"));
customerDB.put("user2@example.com", new Customer("C002", "Bob Smith", "user2@example.com", "pass456"));
}
public static synchronized AuthenticationService getInstance() {
if (instance == null) {
instance = new AuthenticationService();
}
return instance;
}
// Authenticate a customer by email and password.
public Customer authenticate(String email, String password) {
Customer customer = customerDB.get(email);
if (customer != null && customer.getPassword().equals(password)) {
return customer;
}
return null;
}
}
// --------------------- PAYMENT PROCESSING (STRATEGY PATTERN) ---------------------
/*
* Strategy Pattern: PaymentStrategy defines the contract for processing payments.
*/
interface PaymentStrategy {
Payment processPayment(double amount);
}
// Concrete strategy for Credit Card payments.
class CreditCardPaymentStrategy implements PaymentStrategy {
@Override
public Payment processPayment(double amount) {
// Simulate processing.
Payment payment = new Payment(UUID.randomUUID().toString(), amount, "CreditCard");
payment.markAsSuccessful();
System.out.println("Processed Credit Card payment for $" + amount);
return payment;
}
}
// Concrete strategy for PayPal payments.
class PayPalPaymentStrategy implements PaymentStrategy {
@Override
public Payment processPayment(double amount) {
// Simulate processing.
Payment payment = new Payment(UUID.randomUUID().toString(), amount, "PayPal");
payment.markAsSuccessful();
System.out.println("Processed PayPal payment for $" + amount);
return payment;
}
}
/*
* Singleton Pattern: PaymentService uses a PaymentStrategy to process payments.
*/
class PaymentService {
private static PaymentService instance;
private PaymentStrategy paymentStrategy;
private PaymentService() {
// Default strategy.
paymentStrategy = new CreditCardPaymentStrategy();
}
public static synchronized PaymentService getInstance() {
if (instance == null) {
instance = new PaymentService();
}
return instance;
}
// Allow changing the payment strategy if needed.
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public Payment processPayment(double amount) {
return paymentStrategy.processPayment(amount);
}
}
// --------------------- OBSERVER PATTERN FOR ORDER NOTIFICATIONS ---------------------
/*
* Observer Pattern: OrderObserver defines the method for receiving order updates.
*/
interface OrderObserver {
void update(Order order, String message);
}
// Concrete observer that simulates sending an email notification.
class EmailOrderObserver implements OrderObserver {
@Override
public void update(Order order, String message) {
System.out.println("Email to " + order.getCustomer().getEmail() + ": Order "
+ order.getOrderId() + " - " + message);
}
}
// --------------------- ORDER MANAGEMENT ---------------------
/*
* Singleton & Factory Pattern: OrderManager handles order creation and processing.
* It updates the inventory and notifies observers of order events (Observer Pattern).
*/
class OrderManager {
private static OrderManager instance;
private List<OrderObserver> observers; // Registered observers.
private OrderManager() {
observers = new ArrayList<>();
}
public static synchronized OrderManager getInstance() {
if (instance == null) {
instance = new OrderManager();
}
return instance;
}
// Register an observer.
public void registerObserver(OrderObserver observer) {
observers.add(observer);
}
// Notify all observers about an order event.
private void notifyObservers(Order order, String message) {
for (OrderObserver observer : observers) {
observer.update(order, message);
}
}
// Create an order for a customer based on a mapping of productId to quantity.
public Order createOrder(Customer customer, Map<String, Integer> itemsToOrder) {
ProductCatalog catalog = ProductCatalog.getInstance();
Order order = new Order(UUID.randomUUID().toString(), customer);
// Process each item in the order.
for (Map.Entry<String, Integer> entry : itemsToOrder.entrySet()) {
String productId = entry.getKey();
int quantity = entry.getValue();
Product product = catalog.getProductById(productId);
if (product != null) {
// Check and update inventory.
if (product.reduceStock(quantity)) {
OrderItem orderItem = new OrderItem(product, quantity);
order.addItem(orderItem);
} else {
System.out.println("Insufficient stock for product: " + product.getName());
}
}
}
// Add order to customer's history.
customer.addOrder(order);
// Notify observers that a new order has been placed.
notifyObservers(order, "Order placed successfully.");
return order;
}
// Cancel an order and restore inventory.
public void cancelOrder(Order order) {
if (order.getStatus() != OrderStatus.CANCELLED) {
for (OrderItem item : order.getItems()) {
item.getProduct().increaseStock(item.getQuantity());
}
order.cancelOrder();
notifyObservers(order, "Order has been cancelled.");
}
}
}
// --------------------- MAIN APPLICATION ---------------------
public class ECommerceApp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// --------------------- User Authentication ---------------------
AuthenticationService authService = AuthenticationService.getInstance();
System.out.println("Welcome to the E-commerce Website!");
System.out.println("Please log in with your email:");
String email = scanner.nextLine();
System.out.println("Enter your password:");
String password = scanner.nextLine();
Customer customer = authService.authenticate(email, password);
if (customer == null) {
System.out.println("Authentication failed. Exiting...");
scanner.close();
return;
}
System.out.println("Welcome, " + customer.getName() + "!");
// --------------------- Product Catalog Display ---------------------
ProductCatalog catalog = ProductCatalog.getInstance();
System.out.println("\nProduct Catalog:");
for (Product product : catalog.getAllProducts()) {
System.out.println(product.getProductId() + " - " + product.getName() +
" | Price: $" + product.getPrice() +
" | Stock: " + product.getStock());
}
// --------------------- Order Processing ---------------------
OrderManager orderManager = OrderManager.getInstance();
// Register an observer for order notifications.
orderManager.registerObserver(new EmailOrderObserver());
Map<String, Integer> itemsToOrder = new HashMap<>();
System.out.println("\nEnter product IDs and quantities to order (e.g., P001 2). Type 'done' when finished:");
while (true) {
String input = scanner.nextLine();
if (input.equalsIgnoreCase("done")) break;
String[] parts = input.split(" ");
if (parts.length != 2) {
System.out.println("Invalid input. Please enter productId and quantity separated by a space.");
continue;
}
String productId = parts[0];
int qty;
try {
qty = Integer.parseInt(parts[1]);
} catch (NumberFormatException e) {
System.out.println("Invalid quantity. Please try again.");
continue;
}
itemsToOrder.put(productId, qty);
}
Order order = orderManager.createOrder(customer, itemsToOrder);
if (order.getItems().isEmpty()) {
System.out.println("No valid items in order. Exiting.");
scanner.close();
return;
}
System.out.println("Order placed successfully! Order ID: " + order.getOrderId());
System.out.println("Order Total: $" + order.getTotalAmount());
// --------------------- Payment Processing ---------------------
PaymentService paymentService = PaymentService.getInstance();
System.out.println("\nProcessing payment...");
Payment payment = paymentService.processPayment(order.getTotalAmount());
if (payment.isSuccessful()) {
System.out.println("Payment successful! Payment ID: " + payment.getPaymentId());
} else {
System.out.println("Payment failed!");
}
// --------------------- Order Summary ---------------------
System.out.println("\nOrder Summary:");
for (OrderItem item : order.getItems()) {
System.out.println(item.getProduct().getName() + " x " + item.getQuantity() +
" | Unit Price: $" + item.getUnitPrice() +
" | Total: $" + item.getTotalPrice());
}
scanner.close();
}
}
3. Explanation of Key Points & Design Patterns
Entities and Relationships
Product, Customer, Order, OrderItem, Payment:
These classes model the key entities in the system. Products hold catalog information and stock. Customers have authentication credentials and order histories. Orders are composed of order items (each referring to a product).
Product Management
ProductCatalog (Singleton):
Centralizes product and inventory management.
OrderManager (Singleton & Factory):
Creates orders from customer input, updates inventory, and registers order status changes.
PaymentService & PaymentStrategy (Strategy Pattern):
Process payments using interchangeable strategies (e.g., Credit Card vs. PayPal).
Observer Pattern for Order Notifications
OrderObserver & EmailOrderObserver:
The Observer pattern is implemented to notify customers of order events.
OrderManager:
Notifies all registered observers when an order is placed or cancelled.
4. Conclusion
This LLD for an E-commerce Website demonstrates:
How major entities (Product, Customer, Order, etc.) and their relationships are modeled.
How product catalog management and inventory are integrated into order processing.
How secure user authentication and payment processing (using the Strategy Pattern) are handled.
How the Observer Pattern is implemented to notify customers of order updates, ensuring a seamless and responsive user experience.
This design employs multiple design patterns (Singleton, Factory, Strategy, and Observer) to ensure modularity, maintainability, and scalability—a robust blueprint suitable for technical interviews and real-world applications.