Skip to content

This project is based on the use of State Machine module of Spring Framework

Notifications You must be signed in to change notification settings

G10xy/Library-Management

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Library Management System

A Spring Boot application demonstrating Spring State Machine for managing book lifecycle in a library system. This project showcases event-driven architecture, state transitions with guards and actions, and async email notifications.


Overview

This project demonstrates how to use Spring State Machine to model a real-world domain problem: managing the lifecycle of books in a library. Instead of manually checking and updating book states with conditional logic scattered across the codebase, the state machine provides:

  • Declarative transitions: Define valid state changes in one place
  • Guards: Business rules that must pass before a transition occurs
  • Actions: Side effects triggered on state entry/exit or during transitions
  • Single Source of Truth: Book state is persisted in the database; state machine is reconstructed on each operation

Key Concepts Demonstrated

Concept Implementation
State Machine Factory Creates state machine instances per book
Guards Business rule validation (e.g., borrow limits)
Entry/Exit Actions Lifecycle hooks (e.g., set dates, log events)
Transition Actions Side effects (e.g., send email on return)
Extended State Contextual data storage (dates, user info)
Event-Driven Async email via Spring Events

State Machine Design

States

State Description
AVAILABLE Book is on shelf, can be borrowed
BORROWED Book is checked out to a user
OVERDUE Borrowed book past due date
ISSUED Permanently assigned (terminal state)

Events

Event Description
BORROW_BOOK User checks out a book
RETURN_BOOK User returns a book
MARK_OVERDUE System marks book as overdue
ISSUE_BOOK Permanent assignment to user

State Transition Diagram

                              RETURN_BOOK [action: email]
                    ┌──────────────────────────────────────────────┐
                    │                                              │
                    │         RETURN_BOOK [action: email]          │
                    │    ┌─────────────────────────────────────┐   │
                    │    │                                     │   │
                    ▼    │                                     │   │
             ┌─────────────┐                                   │   │
  (start) ──▶│  AVAILABLE  │                                   │   │
             └──────┬──────┘                                   │   │
                    │                                          │   │
                    │ BORROW_BOOK                              │   │
                    │ [guard: userBorrowLimitGuard]            │   │
                    │ [guard: userHasNoOverdueGuard]           │   │
                    │                                          │   │
                    ▼                                          │   │
             ┌─────────────┐                                   │   │
             │  BORROWED   │───────────────────────────────────┘   │
             │             │                                       │
             │ entry:      │                                       │
             │  - set borrowDate                                   │
             │  - set dueDate                                      │
             │  - set borrowedByUserId                             │
             │                                                     │
             │ exit:                                               │
             │  - calculate duration                               │
             │  - set returnDate                                   │
             └──────┬──────┘                                       │
                    │                                              │
        ┌───────────┼───────────┐                                  │
        │           │           │                                  │
        │           │           │                                  │
        │ MARK_     │           │ ISSUE_BOOK                       │
        │ OVERDUE   │           │                                  │
        │           │           │                                  │
        ▼           │           ▼                                  │
 ┌─────────────┐    │    ┌─────────────┐                           │
 │   OVERDUE   │    │    │   ISSUED    │ (terminal)                │
 │             │────┼───▶│             │                           │
 │ entry:      │    │    └─────────────┘                           │
 │  - set      │    │                                              │
 │    overdueDate   │                                              │
 │             │    │                                              │
 └──────┬──────┘    │                                              │
        │           │                                              │
        │ RETURN_BOOK [action: email]                              │
        │           │                                              │
        └───────────┴──────────────────────────────────────────────┘

Transition Table

From Event To Guard Action
AVAILABLE BORROW_BOOK BORROWED userBorrowLimitGuard, userHasNoOverdueGuard -
BORROWED RETURN_BOOK AVAILABLE - sendEmailAction
BORROWED MARK_OVERDUE OVERDUE - -
BORROWED ISSUE_BOOK ISSUED - -
OVERDUE RETURN_BOOK AVAILABLE - sendEmailAction

Guards (Business Rules)

Guard Rule
userBorrowLimitGuard User cannot have more than 3 borrowed books
userHasNoOverdueGuard User cannot borrow if they have overdue books

Single Source of Truth Pattern

┌─────────────────────────────────────────────────────────────────┐
│                         Request Flow                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   1. API Request (PATCH /api/v1/books/{id}/borrow)              │
│              │                                                  │
│              ▼                                                  │
│   2. BookStatusChangeService.doAction()                         │
│              │                                                  │
│              ▼                                                  │
│   3. Read current state from BookEntity (DB)  ◄── Single       │
│              │                                    Source of     │
│              ▼                                    Truth         │
│   4. Create StateMachine, reset to DB state                     │
│              │                                                  │
│              ▼                                                  │
│   5. Send event, guards validate, transition executes           │
│              │                                                  │
│              ▼                                                  │
│   6. If successful: persist new state to BookEntity (DB)        │
│              │                                                  │
│              ▼                                                  │
│   7. State machine discarded (transient)                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Getting Started

Prerequisites

  • Java 21 or higher
  • Maven 3.6+

Build and Run

# Clone the repository
git clone <repository-url>
cd library-management

# Build the project
./mvnw clean install

# Run the application
./mvnw spring-boot:run

Access Points

URL Description
http://localhost:8080/swagger-ui.html Swagger UI
http://localhost:8080/api-docs OpenAPI spec
http://localhost:8080/h2-console H2 Database console

H2 Console Access

  • JDBC URL: jdbc:h2:mem:testdb
  • Username: sa
  • Password: (empty)

Key Design Decisions

  1. Single Source of Truth: State machine context is not persisted separately. The BookEntity.state column is the authoritative source. State machines are reconstructed on each request.

  2. Blocking Reactive Calls: We use blockLast() on state machine events to ensure transitions complete before reading the new state. This avoids race conditions.

  3. Business Logic in Guards: State-checking guards are unnecessary (the framework handles this). Guards should validate business rules like borrowing limits.

  4. Async Email: Book return emails are sent asynchronously via Spring's @Async and ApplicationEventPublisher to not block the API response.

Adding a New State

  1. Add state to BookStates.java
  2. Add transitions in StateMachineConfig.configure(transitions)
  3. Add entry/exit actions if needed
  4. Update tests
  5. Update this README

Adding a New Guard

  1. Create a @Bean method returning Guard<BookStates, BookEvents>
  2. Add it to the relevant transition with .guard(yourGuard())
  3. Document the business rule
  4. Add tests

License

This project is for educational purposes, demonstrating Spring State Machine patterns.


References

About

This project is based on the use of State Machine module of Spring Framework

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages