Problem Explanation:-
Products -> Bikes, Scooters
Bike -> 3 sizes (Small, Medium, Large)
Scooter -> 2 styles (Electric, gas)
Oprations:
Queries required:
Commands requied:
Deliverables said:
API spec
Class diagram
Schema diagram
Sequence diagrams(optionl)
Design Approach
We will follow Object-Oriented Design + Database Schema + REST APIs.
Why this approach?
Separation of Concerns → Inventory, Customer, Rentals, Payments are different modules.
Extensibility → Easy to add new product types (e.g., e-bikes, motorcycles).
Maintainability → Clear entity relationships make future queries simple.
Reusability → Interfaces and abstract classes allow plugging in different rental products.
3 — Java classes & interfaces (essential parts)
public enum ProductType{
BIKE, SCOOTER
}
public abstract class Product{
private Long productId;
private ProductType type; //
private boolean available;
private double basePricePerDay;
//getter and setter
public abstract String productDetails();}
public enum Size{
SMALL,
MEDIUM,
LARGE
}
public class Bike extends Product{
private Size size;
//getter and setter}
public enum Style{
ELECTRIC,
GAS
}
public class Scooter extends Product{
private Style style;
//getter and setter}
public class Customer{
private long id;
private String name;
private String email;
private double balance;
//getter and setter}
public class RentalStatus{
ACTIVE, RETURNED, LOST, CANCELLED
}
public class Rental{
private Long id;
private Long productId;
private Long customerId;
private LocalDateTime startAt;
private LocalDateTime dueAt;
private LocalDateTime returnedAt;
private double priceCharged;
private RentalStatus status;
// getter and setter}
public class Charge{
private Long id;
private Long customerId;
private double amount;
private String reason;
private LocalDateTime createdAt;
}
Service interfaces [InventoryService, CustomerService, RentalService]
public interface InventoryService{
Product addProduct(Product product);
void removeProduct(Long productId); // permanent remove logical and physical
Optional<Product> getProduct(Long productId);
List<Product> listAvailableProducts(ProductFilter filter);
long countBikeBySize(Bike.Size size);}
public interface CustomerService{
Customer createCustomer(CustomerCreateDTO dto);
Customer getCustomer(Long id);
double getBalance(Long customerId);
List<Rental> getCustomerRental(Long customerId);}
public interface RentalService{
Rental rentProduct(Long customerId, Long productId, LocalDateTime dueAt) throw NotAvailableException;
Rental returnProduct(Long rentalId, LocalDateTime returnedAt);
List<Rental> listOverdueRentals();}
Tables:
Customer
customer_id (PK)
name
balance
Product
product_id (PK)
type (bike or scooter)
is_available
Bike
bike_id (FK → Product)
size (SMALL, MEDIUM, LARGE)
Scooter
scooter_id (FK → Product)
style (ELECTRIC, GAS)
Rental
rental_id (PK)
product_id (FK → Product)
customer_id (FK → Customer)
start_date
due_date
return_date
charges
✅ Reasoning:
Product table for shared properties.
Bike and Scooter as subtype tables (Single Table Inheritance possible, but we want flexibility).
Rental table links customer ↔ product.
CREATE TABLE product (
id BIGSERIAL PRIMARY KEY,
sku VARCHAR(64) UNIQUE NOT NULL,
type VARCHAR(20) NOT NULL, -- BIKE, SCOOTER, ...
available BOOLEAN DEFAULT TRUE,
base_price_per_day NUMERIC(10,2) DEFAULT 0.0,
created_at TIMESTAMP DEFAULT now(),
deleted_at TIMESTAMP NULL
);
CREATE TABLE bike (
product_id BIGINT PRIMARY KEY REFERENCES product(id),
size VARCHAR(10) NOT NULL -- SMALL, MEDIUM, LARGE
);
CREATE TABLE scooter (
product_id BIGINT PRIMARY KEY REFERENCES product(id),
style VARCHAR(10) NOT NULL -- ELECTRIC, GAS
);
CREATE TABLE customer (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255) UNIQUE,
balance NUMERIC(12,2) DEFAULT 0.0,
created_at TIMESTAMP DEFAULT now()
);
CREATE TABLE rental (
id BIGSERIAL PRIMARY KEY,
product_id BIGINT NOT NULL REFERENCES product(id),
customer_id BIGINT NOT NULL REFERENCES customer(id),
start_at TIMESTAMP NOT NULL DEFAULT now(),
due_at TIMESTAMP NOT NULL,
returned_at TIMESTAMP NULL,
price_charged NUMERIC(10,2) DEFAULT 0.0,
status VARCHAR(20) NOT NULL, -- ACTIVE, RETURNED...
created_at TIMESTAMP DEFAULT now()
);
CREATE TABLE charge (
id BIGSERIAL PRIMARY KEY,
customer_id BIGINT NOT NULL REFERENCES customer(id),
amount NUMERIC(12,2) NOT NULL,
reason VARCHAR(255),
created_at TIMESTAMP DEFAULT now()
);
-- Indexes for queries
CREATE INDEX idx_product_type_available ON product(type, available);
CREATE INDEX idx_bike_size ON bike(size);
CREATE INDEX idx_rental_status_due ON rental(status, due_at);
CREATE INDEX idx_customer_email ON customer(email);
Why normalized?
Avoids storing bike-specific columns in product (redundant nulls).
Makes constraints (bike.size allowed values) explicit.
Easier to add new product types (add table + map to product row).
Inventory APIs
POST /inventory/product → Add a product
DELETE /inventory/product/{id} → Remove product
GET /inventory/products?type=bike&available=true → List available products
GET /inventory/products/bikes/count?size=SMALL → Query small bikes
Customer APIs
POST /customers → Add customer
GET /customers/{id} → customer details
GET /customers/{id}/balance → Check balance
GET /customers/{id}/rentals → Get customer rentals
Rental APIs
POST /rentals → Rent a product (customerId, productId, dueDate)
POST /rentals/{id}/return → Return a product
GET /rentals/overdue → Find overdue rentals
Charges
POST customers/{id}/charges → create a charge (e.g., late fee)
GET customers/{id}/ledger → list charges
✅ Reasoning:
REST fits naturally with resources (Customer, Product, Rental).
Queries map well to GET requests.
Commands map well to POST/DELETE.
How many small bikes?
→ SELECT COUNT(*) FROM Bike WHERE size='SMALL' AND product_id IN (SELECT product_id FROM Product WHERE is_available=true);
What products for rent?
→ GET /inventory/products
Does customer owe balance?
→ GET /customers/{id}/balance
What products are rented?
→ GET /rentals
Overdue rentals?
→ GET /rentals/overdue
What products has a customer rented?
→ GET /customers/{id}/rentals
===============
ToDos :-
Entities Class must
Database Schemas
API designs
enum ProductType{
BIKE,
SCOOTER
}
enum BikeSize{
SMALL,
MEDIUM,
LARGE
}
enum ScootorStyle{
ELECTIRC,
GAS
}
abstract class Product{
private String productId;
private ProductType type;
privateboolean isAvailable;
// private double perDayCharge;
//getter setter
}
class Bike extends Product{
BikeSize size;
// getter setter}
class Scooter extends Product{
ScootorStyle style;
// getter setter}
class Customer{
String customerId;
String name;
String phoneNumber;
String balance;
// getter setter}
class Rental{
String rentalId;
String customerId;
String productId;
LocalDateTime startDate;
LocalDateTime endDate;
LocalDateTime returnedDate;
double charge;
// getter setter}
Database Schemas:-
Customer
Product
Bike
Scooter
// Update
Rental
Charge
inteface Inventory{
Product addProduct(Product product);
void removeProduct(String productId);
List getAllAvaialbleProducts();
int getBikeBySize(BikeSize size);
}
inteface Customer{
Customer addCustomer(Customer customer);
double getCustomerBalance(String customerId);
List getCustomerRentedProducts(String customerId);
}
inteface Rental{
Rental rentProduct(String customerId, String productId, LocalDateTime endDate);
void returnProduct(String productId, LocalDateTime currtentDate);
}