OnTheGoRentals: Spring Boot and Vue 3 Platform

Part 1 of our OnTheGoRentals dev diary. In this installment, we break down the modular monolithic architecture, the domain model with immutable entities, and why we chose Builder patterns over mutable state.

What You’ll Learn

  • Modular Monolith Architecture

    Why we chose a clean monolithic blueprint over premature microservices.

  • Immutable Domain Entities

    How Builder patterns enforce state integrity across booking lifecycle transitions.

  • Layered Spring Boot Architecture

    Controllers, Services, Repositories — the standard Spring layout that scales.

Why We Built This

OnTheGoRentals started as an exploration of how far you can push a Spring Boot monolith before needing to split into microservices. We wanted to build a realistic domain — car rental has temporal availability, complex state machines, and concurrent access patterns — while creating something genuinely useful.

The result is a production-deployed platform that’s live and functional. It’s also a reusable template: the booking engine, authentication system, and admin dashboard can be adapted for bicycles, houses, tools, or any rentable asset with minimal changes.

OnTheGoRentals System Architecture

Architecture Selection: Why This Stack?

Our stack consists of Java 21, Spring Boot 3.5.0, and Spring Security 6.5.0 on the backend, with a highly responsive SPA built on Vue.js 3 (Composition API). The backend follows standard Spring Boot layered architecture: Controllers, Services, Repositories, JPA Entities.

Layer Technology Key Files
REST Controllers Spring MVC + Validation BookingController, CarController, AuthController
Service Layer Spring Service + Transactional BookingServiceImpl, CarServiceImpl, AuthServiceImpl
Repository Layer Spring Data JPA + JPQL BookingRepository, CarRepository
Domain Entities JPA + Lombok Builder Booking, Car, User, Rental, DamageReport
Security Spring Security 6.5 + JWT JwtAuthenticationFilter, GoogleOAuth2UserService
Storage MinIO + Local Fallback MinioStorageService, LocalFileStorageService

The Domain Model: Immutable Entities and Builder Patterns

The foundation of OnTheGoRentals is a carefully designed domain model where entities are immutable once created. Every Booking, Car, and User is built using the Builder pattern, ensuring that once an object enters the system, it cannot be accidentally mutated. This design choice forced us to think deeply about state transitions from the very beginning.

The Booking Entity

The Booking entity is the heart of the system. It tracks who rented what, when, and in what state the reservation currently sits:

// Booking.java — Core Domain Entity
@Entity
@Table(name = “booking”)
@Getter
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public class Booking {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(nullable = false)
    private UUID uuid;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = “user_id”, nullable = false)
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = “car_id”, nullable = false)
    private Car car;
    @Column(nullable = false)
    private LocalDateTime startDate;
    @Column(nullable = false)
    private LocalDateTime endDate;
    @Enumerated(EnumType.STRING)
    @Column(nullable = false)
    private BookingStatus status;
    @Column(nullable = false)
    private BigDecimal totalCost;
    @Column(nullable = false)
    private boolean deleted;
}

The @Builder(toBuilder = true) annotation is critical. It allows us to create new instances from existing ones without mutation—essential for status transitions. When a booking moves from PENDING to CONFIRMED, we don’t modify the existing object; we build a new one with the updated status.

The Booking Status Lifecycle

Bookings flow through a defined state machine:

Booking Status Lifecycle

This lifecycle is enforced in BookingServiceImpl, where each status transition is validated before execution. You can’t jump from PENDING to RENTAL_COMPLETED—you must go through CONFIRMED and RENTAL_INITIATED first.

The Live Interface

Here’s the live homepage — clean, focused, and ready for customers:

OnTheGoRentals Live Homepage

The car listing page showing the fleet with search, filtering, and date-based availability:

OnTheGoRentals Car Listing Page

And the car detail page with image carousel, specifications, and booking:

OnTheGoRentals Car Detail Page

Ready for Part 2?

In the next installment, we tackle the hardest problem in rental systems: preventing double-bookings with JPQL interval queries, and building a secure JWT authentication architecture.

Related Reading