Design (LLD) Online Book Management System - Machine Coding Interview
Asked in LLD Interview
Functional Requirements:
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.
Read a Book:
- A user should be able to select a book from the search results and start reading it online.
User Registration and Management:
The system should support user registration.
Registered users should be able to extend their access or subscription.
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:
The system should be scalable to handle multiple users and a large catalog of books in the future.
It should ensure data consistency and proper state management when managing active users and books.
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);
}
}
