Online book store

Online Bookstore: Low-Level Design (LLD) with Design Patterns and Java Code

In this answer, we will design an Online Bookstore system that handles books, patrons, orders, and inventory management. We will demonstrate how various design patterns can be applied to manage these entities, improve efficiency, and scale the application.


Design Overview

The Online Bookstore system will include the following key components:

  1. Book: Represents a book in the store.

  2. Patron: Represents a customer who can browse, order, and pay for books.

  3. Order: Represents a customer's order, including payment and fulfillment.

  4. Inventory: Manages the stock of books and restocking levels.

  5. SearchEngine: Provides the functionality for searching books by title, author, subject, and other attributes.

  6. PaymentProcessor: Handles the payment and transaction processing for orders.

We'll incorporate design patterns such as:

  • Factory Pattern for creating different types of objects.

  • Observer Pattern for notifying patrons about order updates.

  • Strategy Pattern for different search algorithms.

  • Singleton Pattern for managing shared instances like the Inventory and PaymentProcessor.

  • Composite Pattern to manage a collection of books and categories.


Entities and Relationships

1. Book Class:

The Book class represents a single book entity. It will contain attributes like title, author, subject, and stock (number of copies available).

class Book {
    private String title;
    private String author;
    private String subject;
    private double price;
    private int stock;

    public Book(String title, String author, String subject, double price, int stock) {
        this.title = title;
        this.author = author;
        this.subject = subject;
        this.price = price;
        this.stock = stock;
    }

    public String getTitle() { return title; }
    public String getAuthor() { return author; }
    public String getSubject() { return subject; }
    public double getPrice() { return price; }
    public int getStock() { return stock; }
    public void decreaseStock() { if (stock > 0) stock--; }
    public void increaseStock(int quantity) { stock += quantity; }
}

2. Patron Class:

The Patron class represents a customer in the system, with information such as name, email, and a list of orders they've placed.

class Patron {
    private String name;
    private String email;
    private List<Order> orders;

    public Patron(String name, String email) {
        this.name = name;
        this.email = email;
        this.orders = new ArrayList<>();
    }

    public String getName() { return name; }
    public String getEmail() { return email; }
    public List<Order> getOrders() { return orders; }

    public void addOrder(Order order) {
        orders.add(order);
    }
}

3. Order Class:

The Order class represents an order placed by a patron. It contains a list of books, the order's status, and payment information.

import java.util.List;

class Order {
    private Patron patron;
    private List<Book> books;
    private double totalAmount;
    private String status;
    private boolean isPaid;

    public Order(Patron patron, List<Book> books) {
        this.patron = patron;
        this.books = books;
        this.totalAmount = books.stream().mapToDouble(Book::getPrice).sum();
        this.status = "Pending";
        this.isPaid = false;
    }

    public double getTotalAmount() { return totalAmount; }
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
    public boolean isPaid() { return isPaid; }
    public void markPaid() { this.isPaid = true; }

    public void addBook(Book book) {
        books.add(book);
        totalAmount += book.getPrice();
    }

    public List<Book> getBooks() { return books; }
}

4. Inventory Class (Singleton Pattern):

The Inventory class will be a Singleton to manage the stock levels of all books. It will ensure that stock is updated correctly when orders are placed and restocked.

import java.util.HashMap;
import java.util.Map;

class Inventory {
    private static Inventory instance;
    private Map<String, Book> books;

    private Inventory() {
        books = new HashMap<>();
    }

    public static Inventory getInstance() {
        if (instance == null) {
            instance = new Inventory();
        }
        return instance;
    }

    public void addBook(Book book) {
        books.put(book.getTitle(), book);
    }

    public Book getBook(String title) {
        return books.get(title);
    }

    public void updateStock(String title, int quantity) {
        Book book = books.get(title);
        if (book != null) {
            book.increaseStock(quantity);
        }
    }

    public boolean isStockAvailable(String title, int quantity) {
        Book book = books.get(title);
        return book != null && book.getStock() >= quantity;
    }
}

5. SearchEngine Class (Strategy Pattern):

The SearchEngine class provides the ability to search for books by various attributes like title, author, and subject. We will use the Strategy Pattern to allow different search algorithms.

interface SearchStrategy {
    List<Book> search(List<Book> books, String query);
}

class TitleSearch implements SearchStrategy {
    @Override
    public List<Book> search(List<Book> books, String query) {
        return books.stream().filter(book -> book.getTitle().contains(query)).collect(Collectors.toList());
    }
}

class AuthorSearch implements SearchStrategy {
    @Override
    public List<Book> search(List<Book> books, String query) {
        return books.stream().filter(book -> book.getAuthor().contains(query)).collect(Collectors.toList());
    }
}

class SubjectSearch implements SearchStrategy {
    @Override
    public List<Book> search(List<Book> books, String query) {
        return books.stream().filter(book -> book.getSubject().contains(query)).collect(Collectors.toList());
    }
}

class SearchEngine {
    private SearchStrategy searchStrategy;

    public void setSearchStrategy(SearchStrategy searchStrategy) {
        this.searchStrategy = searchStrategy;
    }

    public List<Book> search(List<Book> books, String query) {
        return searchStrategy.search(books, query);
    }
}

6. PaymentProcessor Class (Strategy Pattern):

The PaymentProcessor class handles payment processing. Different payment methods can be implemented using the Strategy Pattern.

interface PaymentStrategy {
    boolean processPayment(Order order);
}

class CreditCardPayment implements PaymentStrategy {
    @Override
    public boolean processPayment(Order order) {
        // Simulate payment processing
        System.out.println("Processing credit card payment of " + order.getTotalAmount());
        return true; // Payment successful
    }
}

class PayPalPayment implements PaymentStrategy {
    @Override
    public boolean processPayment(Order order) {
        // Simulate PayPal payment processing
        System.out.println("Processing PayPal payment of " + order.getTotalAmount());
        return true; // Payment successful
    }
}

class PaymentProcessor {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public boolean processPayment(Order order) {
        return paymentStrategy.processPayment(order);
    }
}

Order Processing and Flow

The Order Processing workflow is as follows:

  1. Placing an Order: A patron selects books to buy. The system checks if the books are available in stock. If available, the order is created.

  2. Payment: Once the order is created, the PaymentProcessor processes the payment through the selected method (e.g., Credit Card, PayPal).

  3. Fulfillment: After payment is successful, the order is marked as "Completed," and the stock is updated.

  4. Inventory Management: If an order is placed, the inventory is updated by decreasing the stock of the books purchased. The system also monitors the stock levels and triggers restocking when stock runs low.


Main Class (Integration)

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        // Initialize inventory with some books
        Inventory inventory = Inventory.getInstance();
        inventory.addBook(new Book("Harry Potter", "J.K. Rowling", "Fantasy", 20.0, 10));
        inventory.addBook(new Book("Clean Code", "Robert C. Martin", "Programming", 30.0, 5));

        // Create a patron (customer)
        Patron patron = new Patron("John Doe", "john.doe@example.com");

        // Create an order
        Book book1 = inventory.getBook("Harry Potter");
        Book book2 = inventory.getBook("Clean Code");
        Order order = new Order(patron, Arrays.asList(book1, book2));

        // Check stock and place order
        if (inventory.isStockAvailable("Harry Potter", 1) && inventory.isStockAvailable("Clean Code", 1)) {
            // Process payment
            PaymentProcessor paymentProcessor = new PaymentProcessor();
            paymentProcessor.setPaymentStrategy(new CreditCardPayment());
            if (paymentProcessor.processPayment(order)) {
                order.setStatus("Completed");
                System.out.println("Order placed successfully!");
            }
        }

        // Search books using strategy pattern
        SearchEngine searchEngine = new SearchEngine();
        searchEngine.setSearchStrategy(new TitleSearch());
        List<Book> searchResults = searchEngine.search(Arrays.asList(book1, book2), "Harry Potter");
        searchResults.forEach(book -> System.out.println("Found: " + book.getTitle()));
    }
}

Explanation of Design Patterns Used

  1. Factory Pattern: Used for creating different types of books, patrons, and payment strategies dynamically.

  2. Singleton Pattern: Used for Inventory and PaymentProcessor to ensure only one instance of these classes is created, shared across the system.

  3. Strategy Pattern: Applied in the SearchEngine and PaymentProcessor classes to allow switching between different search algorithms (e.g., title search, author search) and payment methods (e.g., credit card, PayPal).

  4. Observer Pattern: (Can be added later for notifying patrons about order updates).

  5. Composite Pattern: Could be applied to manage books and categories, treating books and categories uniformly if we need to expand to a more complex category structure in the future.


Conclusion

This Low-Level Design (LLD) of the Online Bookstore demonstrates how to efficiently manage books, patrons, and orders, leveraging design patterns to create a scalable and maintainable system. It handles book searching, inventory management, order processing, and payment handling, all while allowing for future extensions such as adding new search strategies, payment methods, or inventory management techniques.

Last updated