Skip to content

Loan Module

The Loan module demonstrates Bus Mode (CQRS) with business logic, domain rules, and cross-aggregate operations. It manages book borrowing and returns.

Domain Layer

Entity: Loan

The Loan entity references User and Book by ID (not direct object references), maintaining aggregate boundaries:

class Loan extends EntityAbstract
{
    public function isOverdue(DateTimeImmutable $now): bool
    {
        return $this->returnedAt === null && $this->dueAt < $now;
    }

    public function markAsReturned(DateTimeImmutable $returnedAt): void
    {
        if (!$this->status->canBeReturned()) {
            throw LoanAlreadyReturnedException::forLoan($this->id);
        }
        $this->status = LoanStatus::Returned;
        $this->returnedAt = $returnedAt;
    }
}

LoanStatus Enum

enum LoanStatus: string
{
    case Active = 'active';
    case Returned = 'returned';
}

Commands and Queries

Command/Query Purpose
BorrowBookCommand Create a new loan
ReturnBookCommand Mark loan as returned
ListLoansQuery All loans (admin)
MyLoansQuery Current user's loans

Business Rules

  • A book can only be borrowed if its status is Available
  • A loan can only be returned if its status is Active
  • Overdue detection based on current date vs due date

Domain Events

  • LoanCreatedEvent - When a book is borrowed
  • LoanReturnedEvent - When a book is returned

Cross-Aggregate Interaction

When borrowing a book, the handler:

  1. Validates book availability via BookRepository
  2. Creates a new Loan entity
  3. Updates book status to Borrowed
  4. Persists both changes
  5. Dispatches LoanCreatedEvent

Permissions

  • loan.view_all - View all loans (admin only)
  • loan.view_own - View own loans (admin and user)
  • loan.borrow - Borrow and return books

See Also