Loading content...
Over the past five pages, we've built a comprehensive Library Management System piece by piece. Now it's time to step back and see how all the pieces fit together into a cohesive, production-ready design.
What We've Built:
This final page presents the complete design with all components working together, ready for implementation.
By the end of this page, you will be able to: • Visualize the complete system architecture • Understand how services orchestrate entity interactions • Trace through complete workflows from start to finish • Identify extension points for future requirements • Apply this design approach to similar LLD problems
The following diagram shows all entities, their attributes, methods, and relationships. This is the kind of diagram you would present in an LLD interview to demonstrate your complete design.
Reading the Diagram:
While entities encapsulate domain logic, services orchestrate complex operations that span multiple entities. Our design includes four primary services:
| Service | Responsibility | Key Operations |
|---|---|---|
| LibraryService | Core circulation | borrowBook, returnBook, renewBook |
| ReservationService | Reservation lifecycle | createReservation, processReturn, cancel |
| FineService | Fine management | assessFine, processPayment, waiveFine |
| CatalogService | Book management | addBook, updateBook, searchBooks |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
/** * LibraryService - Orchestrates core circulation operations. * This is the main entry point for borrow/return workflows. */class LibraryService { private readonly bookRepository: BookRepository; private readonly memberRepository: MemberRepository; private readonly loanRepository: LoanRepository; private readonly librarianRepository: LibrarianRepository; private readonly reservationService: ReservationService; private readonly fineService: FineService; private readonly eventBus: LibraryEventBus; constructor( bookRepository: BookRepository, memberRepository: MemberRepository, loanRepository: LoanRepository, librarianRepository: LibrarianRepository, reservationService: ReservationService, fineService: FineService, eventBus: LibraryEventBus ) { this.bookRepository = bookRepository; this.memberRepository = memberRepository; this.loanRepository = loanRepository; this.librarianRepository = librarianRepository; this.reservationService = reservationService; this.fineService = fineService; this.eventBus = eventBus; } /** * Complete borrow workflow */ async borrowBook( memberCardNumber: string, bookBarcode: string, librarianId: string ): Promise<BorrowResult> { // 1. Load and validate entities const member = await this.memberRepository .findByCardNumber(memberCardNumber); if (!member) { throw new MemberNotFoundError(`Card: ${memberCardNumber}`); } const librarian = await this.librarianRepository.findById(librarianId); if (!librarian || !librarian.canProcessLoans()) { throw new UnauthorizedOperationError('Librarian cannot process loans'); } const book = await this.bookRepository.findByBarcode(bookBarcode); if (!book) { throw new BookNotFoundError(`Barcode: ${bookBarcode}`); } const copy = book.getCopyByBarcode(bookBarcode); if (!copy) { throw new CopyNotFoundError(`Barcode: ${bookBarcode}`); } // 2. Check member eligibility const eligibility = member.canBorrow(); if (!eligibility.eligible) { throw new BorrowingNotAllowedError(eligibility.reason!); } // 3. Handle reservation cases if (copy.getStatus() === BookCopyStatus.RESERVED) { return this.handleReservedCopyCheckout( member, copy, book, librarian ); } // 4. Validate copy availability if (!copy.isAvailable()) { throw new CopyNotAvailableError( `Copy status: ${copy.getStatus()}` ); } // 5. Check if book has reservations by OTHER members const hasOthersReservations = book.hasPendingReservationsByOthers( member.getId() ); if (hasOthersReservations) { throw new BookReservedError( 'This book has pending reservations' ); } // 6. Create and persist loan const loan = new Loan( this.generateLoanId(), member, copy, librarian ); copy.checkOut(loan); member.addLoan(loan); await Promise.all([ this.loanRepository.save(loan), this.bookRepository.save(book), this.memberRepository.save(member) ]); // 7. Publish event this.eventBus.publish(new BookBorrowedEvent(loan)); return { success: true, loan, dueDate: loan.getDueDate(), wasReservation: false }; } /** * Complete return workflow */ async returnBook(bookBarcode: string): Promise<ReturnResult> { // 1. Find the book and copy const book = await this.bookRepository.findByBarcode(bookBarcode); if (!book) { throw new BookNotFoundError(`Barcode: ${bookBarcode}`); } const copy = book.getCopyByBarcode(bookBarcode); if (!copy) { throw new CopyNotFoundError(`Barcode: ${bookBarcode}`); } // 2. Get active loan const loan = copy.getCurrentLoan(); if (!loan) { throw new NoActiveLoanError('This copy is not currently loaned'); } // 3. Process the return (includes fine calculation via State) const fineResult = loan.returnBook(); const member = loan.getMember(); // 4. Assess fine if overdue if (fineResult.wasOverdue) { await this.fineService.assessFine(loan); } // 5. Process reservation queue const holdResult = await this.reservationService.processReturn(copy); // 6. If no reservation, make copy available if (!holdResult) { copy.checkIn(false); } // 7. Update member loan tracking member.completeLoan(loan); // 8. Persist changes await Promise.all([ this.loanRepository.save(loan), this.bookRepository.save(book), this.memberRepository.save(member) ]); // 9. Publish event this.eventBus.publish(new BookReturnedEvent(loan, fineResult)); return { success: true, fineResult, reservationNotified: holdResult !== null, nextReservationMember: holdResult?.memberToNotify?.getName() }; } /** * Renewal workflow */ async renewBook( memberCardNumber: string, loanId: string ): Promise<RenewalResult> { const member = await this.memberRepository .findByCardNumber(memberCardNumber); if (!member) { throw new MemberNotFoundError(`Card: ${memberCardNumber}`); } const loan = await this.loanRepository.findById(loanId); if (!loan || loan.getMember().getId() !== member.getId()) { throw new LoanNotFoundError('Loan not found for this member'); } const book = loan.getBookCopy().getBook(); const hasReservation = book.hasPendingReservations(); // Delegate to loan state (will throw if not allowed) loan.renew(hasReservation); await this.loanRepository.save(loan); this.eventBus.publish(new LoanRenewedEvent(loan)); return { success: true, newDueDate: loan.getDueDate(), renewalsRemaining: Loan.MAX_RENEWALS - loan.getRenewalCount() }; } /** * Handles checkout when copy is reserved */ private async handleReservedCopyCheckout( member: Member, copy: BookCopy, book: Book, librarian: Librarian ): Promise<BorrowResult> { // Check if THIS member has the ready reservation const memberReservation = await this.reservationService .findReadyReservation(member.getId(), book.getId()); if (!memberReservation) { throw new BookReservedError( 'This copy is held for another member' ); } // Create loan and fulfill reservation const loan = new Loan( this.generateLoanId(), member, copy, librarian ); memberReservation.fulfill(loan); copy.checkOut(loan); member.addLoan(loan); member.removeReservation(memberReservation); await Promise.all([ this.loanRepository.save(loan), this.reservationService.save(memberReservation), this.bookRepository.save(book), this.memberRepository.save(member) ]); this.eventBus.publish(new BookBorrowedEvent(loan)); return { success: true, loan, dueDate: loan.getDueDate(), wasReservation: true }; } private generateLoanId(): string { return `LOAN-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; }}Sequence diagrams show the temporal flow of operations. These diagrams are excellent for interview explanations as they show exactly what happens step by step.
This sequence shows the complete happy path for borrowing a book, from member identification through loan creation.
This sequence shows what happens when a returned book has pending reservations—the system notifies the next member and places the book on hold.
A clean project structure separates concerns and makes the codebase navigable. Here's a recommended layout following clean architecture principles:
library-management-system/├── src/│ ├── domain/ # Core business logic (innermost layer)│ │ ├── entities/│ │ │ ├── Book.ts│ │ │ ├── BookCopy.ts│ │ │ ├── Member.ts│ │ │ ├── Loan.ts│ │ │ ├── Librarian.ts│ │ │ ├── Reservation.ts│ │ │ ├── Fine.ts│ │ │ ├── FinePayment.ts│ │ │ └── Author.ts│ │ ││ │ ├── enums/│ │ │ ├── BookCopyStatus.ts│ │ │ ├── MemberStatus.ts│ │ │ ├── LoanStatus.ts│ │ │ ├── ReservationStatus.ts│ │ │ ├── FineStatus.ts│ │ │ └── LibrarianRole.ts│ │ ││ │ ├── states/ # State pattern implementations│ │ │ ├── LoanState.ts # Interface│ │ │ ├── ActiveLoanState.ts│ │ │ ├── OverdueLoanState.ts│ │ │ ├── ReturnedLoanState.ts│ │ │ └── LostLoanState.ts│ │ ││ │ ├── policies/ # Strategy pattern implementations│ │ │ ├── MembershipPolicy.ts│ │ │ ├── StudentMembershipPolicy.ts│ │ │ ├── FacultyMembershipPolicy.ts│ │ │ ├── GeneralMembershipPolicy.ts│ │ │ ├── FinePolicy.ts│ │ │ └── StandardFinePolicy.ts│ │ ││ │ ├── events/ # Domain events│ │ │ ├── LibraryEvent.ts│ │ │ ├── BookBorrowedEvent.ts│ │ │ ├── BookReturnedEvent.ts│ │ │ ├── ReservationReadyEvent.ts│ │ │ └── FineAssessedEvent.ts│ │ ││ │ ├── errors/ # Domain-specific errors│ │ │ ├── BookNotFoundError.ts│ │ │ ├── BorrowingNotAllowedError.ts│ │ │ └── ...│ │ ││ │ └── value-objects/│ │ └── Address.ts│ ││ ├── application/ # Use cases / Application services│ │ ├── services/│ │ │ ├── LibraryService.ts│ │ │ ├── ReservationService.ts│ │ │ ├── FineService.ts│ │ │ └── CatalogService.ts│ │ ││ │ └── interfaces/ # Repository interfaces│ │ ├── BookRepository.ts│ │ ├── MemberRepository.ts│ │ ├── LoanRepository.ts│ │ └── ...│ ││ ├── infrastructure/ # External concerns│ │ ├── persistence/│ │ │ ├── InMemoryBookRepository.ts│ │ │ ├── PostgresBookRepository.ts│ │ │ └── ...│ │ ││ │ ├── messaging/│ │ │ ├── LibraryEventBus.ts│ │ │ └── observers/│ │ │ ├── EmailNotificationObserver.ts│ │ │ ├── AuditLogObserver.ts│ │ │ └── AnalyticsObserver.ts│ │ ││ │ └── config/│ │ └── PolicyFactory.ts│ ││ └── presentation/ # API / UI layer│ ├── controllers/│ │ ├── CirculationController.ts│ │ ├── CatalogController.ts│ │ └── MemberController.ts│ ││ └── dto/│ ├── BorrowRequest.ts│ ├── BorrowResponse.ts│ └── ...│├── tests/│ ├── unit/│ │ ├── entities/│ │ ├── services/│ │ └── states/│ ││ └── integration/│ └── workflows/│└── package.jsonDomain Layer: Pure business logic with no external dependencies. Entities, value objects, domain events, and policies live here.
Application Layer: Orchestrates domain objects to fulfill use cases. Contains service classes and repository interfaces.
Infrastructure Layer: Implements interfaces from application layer. Database access, messaging, external services.
Presentation Layer: HTTP controllers, CLI handlers, or any user-facing interface.
Dependencies flow inward: Presentation → Infrastructure → Application → Domain
Before presenting any LLD design, validate it against these criteria. A thorough self-review catches issues before your interviewer does.
| Category | Check | Our Design Status |
|---|---|---|
| Completeness | All requirements addressed? | ✅ All FR- and NFR- requirements mapped to design elements |
| Completeness | Core use cases covered? | ✅ Borrow, Return, Renew, Reserve, Pay Fine all designed |
| SOLID - S | Single Responsibility? | ✅ Each class has one reason to change |
| SOLID - O | Open/Closed? | ✅ Strategy and Observer enable extension without modification |
| SOLID - L | Liskov Substitution? | ✅ Policy implementations are interchangeable |
| SOLID - I | Interface Segregation? | ✅ Focused interfaces (LoanState, MembershipPolicy) |
| SOLID - D | Dependency Inversion? | ✅ Services depend on repository interfaces, not implementations |
| Patterns | Appropriate pattern use? | ✅ Observer, State, Strategy applied to real problems |
| Patterns | Not over-engineered? | ✅ No patterns applied without clear benefit |
| Relationships | Clear ownership semantics? | ✅ Composition vs Aggregation properly applied |
| Relationships | Aggregate boundaries defined? | ✅ Book and Member as aggregate roots |
| Extensibility | Easy to add membership types? | ✅ New MembershipPolicy class only |
| Extensibility | Easy to add notification channels? | ✅ New Observer subscription only |
| Error Handling | Edge cases addressed? | ✅ Concurrency, lost books, expired holds |
| Auditability | Actions traceable? | ✅ Events published for all state changes |
A well-designed system anticipates change. Our Library Management System has clear extension points for common enhancements:
| Future Requirement | Extension Point | Changes Needed |
|---|---|---|
| E-books/Digital Media | Create EBook extends LibraryItem base class | Introduce LibraryItem hierarchy; EBook with different loan mechanics |
| Multi-branch libraries | Add Branch entity, associate with BookCopy | BookCopy gains branchId; inter-branch transfer workflow |
| Self-checkout kiosks | New presentation layer controller | KioskController using same LibraryService; different authentication |
| Mobile app | REST API in presentation layer | Controllers exposed as REST endpoints; same service layer |
| Email delivery | Implement EmailService for Observer | EmailNotificationObserver calls real email service |
| Payment gateway | Implement PaymentGateway interface | FineService integrates with Stripe/PayPal wrapper |
| Late fee waiver rules | Add automated WaiverPolicy strategy | FineService checks WaiverPolicy before applying fine |
| Reading lists/curriculum | Add ReadingList entity linking Members to Books | New entity and service; reference from Member |
In interviews, briefly mention extensibility: "This design could easily support e-books by introducing a LibraryItem base class, or multi-branch operations by associating copies with branches. The Strategy pattern for membership policies means adding new member types requires no changes to the Member class." This demonstrates forward-thinking design.
LLD interviews typically last 45-60 minutes. You won't have time to present everything we've covered. Here's a structured approach:
Congratulations! You've completed a comprehensive, production-quality design of a Library Management System. Let's recap what we've accomplished:
The Approach is Transferable:
The methodology you've learned applies to any LLD problem:
The next case studies in this chapter follow the same approach with different domains.
You now have a complete, interview-ready Library Management System design. This design demonstrates all core LLD skills: entity modeling, relationship design, pattern application, and handling complex business logic. Use this as a template for approaching similar problems in interviews and real projects. Next up: Elevator System design, where we'll tackle state machines and scheduling algorithms.