Try Our Course for 4 Hours @99rs

Try our course for 4 Hours and if you like it, you can go for one year or lifetime access. If you buy our (1yr or lifetime) course 99rs will be refunded !

Design (LLD) Online Book Management System - Machine Coding Interview

Design (LLD) Online Book Management System - Machine Coding Interview

Asked in LLD Interview

Functional Requirements:

  1. Search for a Book:

    • Users should be able to search for books in the system based on various criteria, such as title, author, or genre.
  2. Read a Book:

    • A user should be able to select a book from the search results and start reading it online.
  3. User Registration and Management:

    • The system should support user registration.

    • Registered users should be able to extend their access or subscription.

  4. Concurrency Constraints:

    • Only one active user can interact with the system at a time.

    • Each user can have only one active book assigned for reading at any given time.

Non-Functional Requirements:

  1. The system should be scalable to handle multiple users and a large catalog of books in the future.

  2. It should ensure data consistency and proper state management when managing active users and books.

  3. The design should incorporate thread safety for concurrent user actions.

Solution

Focus of this solution is on 4th Requirement.

import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

// Enum for Book Status
enum BOOK_STATUS {
    FREE, ISSUED
}

// Abstract class for Person
abstract class Person {
    String personId;

    public Person(String personId) {
        this.personId = personId;
    }

    public String getPersonId() {
        return personId;
    }
}

// User Class
class User extends Person {
    private Book currentIssuedBook;

    public User(String personId) {
        super(personId);
    }

    public Book getCurrentIssuedBook() {
        return currentIssuedBook;
    }

    public void setCurrentIssuedBook(Book book) {
        this.currentIssuedBook = book;
    }
}

// Abstract class for Book
abstract class Book {
    String bookId;
    BOOK_STATUS status;

    public Book(String bookId) {
        this.bookId = bookId;
        this.status = BOOK_STATUS.FREE;
    }

    public String getBookId() {
        return bookId;
    }

    public BOOK_STATUS getStatus() {
        return status;
    }

    public void setStatus(BOOK_STATUS status) {
        this.status = status;
    }
}

// Concrete Classes for Books
class ComicBook extends Book {
    public ComicBook(String bookId) {
        super(bookId);
    }
}

class OthersBook extends Book {
    public OthersBook(String bookId) {
        super(bookId);
    }
}

// Factory for creating Users
class UserFactory {
    public static User createUser(String personId) {
        return new User(personId);
    }
}

// Factory for creating Books
class BookFactory {
    public static Book createBook(String bookType, String bookId) {
        switch (bookType) {
            case "Comic":
                return new ComicBook(bookId);
            default:
                return new OthersBook(bookId);
        }
    }
}

// Interface for Book Management
interface BookManage {
    boolean issueBook(Book book, User user);
    boolean returnBook(User user);
    List<Book> searchBooks(String query);
}

// Book Management System Implementation
class BookManagementSystem implements BookManage {
    private final Map<String, Book> bookDatabase = new HashMap<>();
    private final AtomicReference<User> activeUser = new AtomicReference<>(null);

    public BookManagementSystem() {
        // Initialize books in the database
        bookDatabase.put("B001", BookFactory.createBook("Comic", "B001"));
        bookDatabase.put("B002", BookFactory.createBook("Others", "B002"));
        bookDatabase.put("B003", BookFactory.createBook("Comic", "B003"));
    }

    // Issue a book to a user
    @Override
    public synchronized boolean issueBook(Book book, User user) {
        if (!setActiveUser(user)) {
            System.out.println("Only one active user is allowed at a time.");
            return false;
        }
        if (user.getCurrentIssuedBook() != null) {
            System.out.println("User already has an issued book.");
            return false;
        }
        if (book.getStatus() == BOOK_STATUS.ISSUED) {
            System.out.println("Book is already issued.");
            return false;
        }
        book.setStatus(BOOK_STATUS.ISSUED);
        user.setCurrentIssuedBook(book);
        System.out.println("Book " + book.getBookId() + " issued to user " + user.getPersonId());
        return true;
    }

    // Return a book
    @Override
    public synchronized boolean returnBook(User user) {
        if (!isActiveUser(user)) {
            System.out.println("Only the active user can return a book.");
            return false;
        }
        Book book = user.getCurrentIssuedBook();
        if (book == null) {
            System.out.println("User has no issued book to return.");
            return false;
        }
        book.setStatus(BOOK_STATUS.FREE);
        user.setCurrentIssuedBook(null);
        clearActiveUser();
        System.out.println("Book " + book.getBookId() + " returned by user " + user.getPersonId());
        return true;
    }

    // Search for books (simple title contains query search)
    @Override
    public List<Book> searchBooks(String query) {
        List<Book> result = new ArrayList<>();
        for (Book book : bookDatabase.values()) {
            if (book.getBookId().contains(query)) {
                result.add(book);
            }
        }
        return result;
    }

    // Set an active user
    private boolean setActiveUser(User user) {
        return activeUser.compareAndSet(null, user);
    }

    // Clear the active user
    private void clearActiveUser() {
        activeUser.set(null);
    }

    // Check if the given user is the active user
    private boolean isActiveUser(User user) {
        return activeUser.get() == user;
    }
}

// Main Class
public class Solution {
    public static void main(String[] args) {
        BookManagementSystem system = new BookManagementSystem();

        User user1 = UserFactory.createUser("U001");
        User user2 = UserFactory.createUser("U002");

        Book book1 = system.searchBooks("B001").get(0);

        // Test Case 1: Issue book to user1
        system.issueBook(book1, user1);

        // Test Case 2: Try issuing another book to user1
        Book book2 = system.searchBooks("B002").get(0);
        system.issueBook(book2, user1);

        // Test Case 3: Try issuing a book to user2 while user1 is active
        system.issueBook(book2, user2);

        // Test Case 4: Return book by user1
        system.returnBook(user1);

        // Test Case 5: Issue book to user2 after user1 returns their book
        system.issueBook(book2, user2);
    }
}

Drawbacks of the Current Implementation (Will be covered in our LLD Course)

  1. Rigid Constraint on Active Users:

    • The current implementation enforces a single active user at any given time using an AtomicReference<User>. While this meets the requirements, it is not flexible enough to handle scenarios where you may need to allow x active users in the future.

    • For example:

      • Scenario: What if the system evolves to allow multiple concurrent users (e.g., x users reading or issuing books simultaneously)?

      • Limitation: The AtomicReference<User> approach cannot track or manage multiple active users.

  2. No Support for Active User Prioritization:

    • Scenario: If x users are allowed simultaneously, how do you prioritize users (e.g., based on membership level, usage time, or queue)?

    • Limitation: The current implementation does not support advanced features like priority queues or user-level policies.

  3. Complexity in Expanding Active Users:

    • Scenario: Expanding to support x active users requires a complete redesign of the active user management logic, potentially introducing bugs and inefficiencies.

    • Limitation: The current system is not built with scalability in mind, making it harder to extend without significant refactoring.

  4. Single User's Responsibility for Book Return:

    • The implementation tightly couples book return with the concept of the active user. While this is valid now, it becomes problematic in a system where multiple users can be active, as the returnBook logic needs to be rewritten to account for x users.
  5. Potential Performance Bottleneck:

    • The synchronized methods (issueBook, returnBook) may lead to performance issues if expanded to handle multiple active users, as threads will have to wait for locks, reducing efficiency.

    • Scenario: If 10 users are trying to issue books simultaneously, the current design serializes these requests, creating bottlenecks.