Determine the different ways the system will be used. This includes main functions the system needs to perform and who will use it.
Functional:
Nonfunctional:
Based on the requirements and use cases, identify the main objects of the system...
Core Object:
Architecture:
Front end: mobile app
alert system: ios alert and firebase
back end server
db: sql
payment: stripe, local machine but i dont know much about it
server site stat: desktop application
Determine how these objects will interact with each other to fulfill the use cases...
key relationship: parking spot assigned to vehicle, vehicle belongs to user, alert notified to user, alert related to payment, payment made by user, all consumption creates logging, logging feeded to external microservice and created stat
cardinality: user:vehicle = one to many; vehicle:user = many to one; spot:vehicle = one to one; user:payment = one to one; alert:user = many to many
data flow:
Design inheritance trees where applicable to promote code reuse and polymorphism. This step involves identifying common attributes and behaviors that can be abstracted into parent classes...
they shared id, createdAt, LastUpdate, as the parent object
Consider using design patterns (e.g., Factory, Singleton, Observer, Strategy) that fit the problem...
Factory Pattern:
addUser(), removeUser(),
addVehicle(), removeVehicle(),
Singleton Pattern:
updateAvailability()
createPayment()
processPayment()
updateStatus()
Strategy Pattern:
feeCalculation()
observer pattern:
update availability <-> pending after ttl
logging generation and stat generation
error generation
command pattern:
check in
check out
generate alert
public abstract class BaseEntity {
protected String id;
protected LocalDateTime createdTime;
protected LocalDateTime updatedTime;
public BaseEntity() {
this.id = UUID.randomUUID().toString();
this.createdTime = LocalDateTime.now();
this.updatedTime = LocalDateTime.now();
}
public void updateTimestamp() {
this.updatedTime = LocalDateTime.now();
}
}
public class User Extends BaseEntity {
private String userId;
private String name;
private String phoneNumber;
private String email;
private Set<String> vehicleSet;
public User(String name, String phoneNumber, String email) {
super();
this.userId = id;
this.phoneNumber = phoneNumber;
this.email = email;
vehicleSet = new HashSet<>();
}
public String getUserId() {
return userId;
}
public void addVehicle(String vehicleId) {
this.vehicleSet.add(vehicleId);
}
public void removeVehicle(String vehicleId) {
this.vehicleSet.remove(vehicleId);
}
public Set<String> getVehicles (String userId) {
return userId.vehicleSet;
}
}
public class Vehicle extends BaseEntity {
private String vehicleId;
private String userId;
private String licensePlate;
private String size;
public Vehicle(String userId, String licensePlate, String size) {
super();
this.vehicleId = id;
this.userId = userId;
this.licensePlate = licensePlate;
this.size = size;
}
public String getVehicleId() {
return vehicleId;
}
public String getUserId() {
return vehicle.userId;
}
}
public class ParkingSpot extends BaseEntity {
private String spotId;
private String size;
private int availability; // -1 booked, 0 pending, 1 available
private int floorNumber;
private LocalDateTime lastUpdate;
private String vehicleId;
public ParkingSpot(String size, int floorNumber) {
super();
this.spotId = size + id;
this.size = size;
this.floorNumber = floorNumber;
this.availability = 1; // Default to available
}
public void assignVehicle(String vehicleId) {
this.vehicleId = vehicleId;
this.availability = 0; // Mark as pending
lastUpdate = LocalDateTime.now();
}
public void bookVehicle(String vehicleId) {
this.vehicleId = vehicleId;
this.availability = -1; // Mark as booked
lastUpdate = LocalDateTime.now();
}
public void releaseSpot() {
this.vehicleId = null;
this.availability = 1; // Mark as available
lastUpdate = LocalDateTime.now();
}
}
public class UserFactory {
private Connection connection;
private VehicleFactory vehicleFactory;
private User user;
public UserFactory(Connection connection, VehicleFactory vehicleFactory) {
this.connection = connection;
this.vehicleFactory = vehicleFactory;
}
public User addUser(String name, String phoneNumber, String email) {
User user = new User(name, phoneNumber, email);
SaveToDatabase(user);
return user;
}
public void deleteUser(String userId) {
deleteFromDatase(userId);
}
private void saveToDatabase(User user) {
String sql = "INSERT INTO Users (userId, name, phoneNumber, email, createdAt, lastUpdate) VALUES (?, ?, ?, ?, ?, ?)";
try () {
System.out.println("User saved to database: " + user.getUserId());
} catch (Exception e) {
e.printStackTrace();
}
}
private void deleteFromDatabase(String userId) {
vehicleFactory.deleteVehicles(user.getVehicles());
// Now delete the user
String sql = "DELETE FROM Users WHERE userId = ?";
try () {
System.out.println("User deleted from database: " + user.getUserId());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public class VehicleFactory {
private Connection connection;
private Vehicle vehicle;
public VehicleFactory(Connection connection) {
this.connection = connection;
}
public Vehicle addVehicle(String userId, String licensePlate, String size) {
Vehicle vehicle = new Vehicle(userId, licensePlate, size);
saveToDatabase(vehicle);
return vehicle;
}
public void removeVehicle(String vehicleId) {
User user = vehicle.getUserId();
user.removeVehicle(vehicleId);
deleteFromDatabase(vehicleId);
}
public void removeVehicles(Set<String> vehicleSet) {
for (String v : vehicleSet) {
removeVehicle(v);
}
}
private void saveToDatabase(Vehicle vehicle) {
String sql = "INSERT INTO Vehicles (vehicleId, userId, licensePlate, size, createdAt, lastUpdate) VALUES (?, ?, ?, ?, ?, ?)";
try () {
System.out.println("Vehicle saved to database: " + vehicle.getVehicleId());
} catch (SQLException e) {
e.printStackTrace();
}
}
private void deleteFromDatabase(String vehicleId) {
String sql = "DELETE FROM Vehicles WHERE vehicleId = ?";
try () {
System.out.println("Vehicle deleted from database: " + vehicleId);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public class Payment extends BaseEntity {
private String paymentId;
private String spotSize;
private LocalDateTime checkInTime;
private LocalDateTime expiredTime;
private LocalDateTime checkOutTime;
private float unitPrice;
private float amount;
private String transactionId;
private boolean paymentStatus;
public Payment(String paymentId, String spotSize, LocalDateTime checkInTime, LocalDateTime expiredTime, float unitPrice) {
super();
this.paymentId= paymentId;
this.spotSize = spotSize;
this.checkInTime = checkInTime;
this.expiredTime = expiredTime;
this.unitPrice = unitPrice;
this.paymentStatus = false;
}
public void completePayment(float amount, String transactionId) {
this.amount = amount;
this.transactionId = transactionId;
this.paymentStatus = true;
this.updateTimestamp();
}
}
public class Alert extends BaseEntity {
private String message;
private String userId;
private LocalDateTime timestamp;
private boolean isResolved;
private String transactionId;
public Alert(String message, String userId) {
super();
this.message = message;
this.userId = userId;
this.isResolved = false;
this.timestamp = LocalDateTime.now();
}
public void resolveAlert() {
this.isResolved = true;
this.updateTimestamp();
}
}
public class Stat extends BaseEntity {
private LocalDate reportDate;
private String smallAvailability;
private String mediumAvailability;
private String largeAvailability;
private float totalFillingRate;
private float totalRevenue;
private int checkin;
private int checkout;
public Stat(LocalDate reportDate) {
super();
this.reportDate = reportDate;
}
public void updateStats(String smallAvail, String mediumAvail, String largeAvail, float fillingRate, float revenue) {
this.smallAvailability = smallAvail;
this.mediumAvailability = mediumAvail;
this.largeAvailability = largeAvail;
this.totalFillingRate = fillingRate;
this.totalRevenue = revenue;
this.updateTimestamp();
}
}
public class FeeStrategy {
public double feeCalculation(String spotSize, LocalDateTime checkIn, LocalDateTime checkOut) {
double ratePerHour = getRateForSize(spotSize);
long hours = Duration.between(checkIn, checkOut).toHours();
return ratePerHour * Math.max(hours, 1); // At least 1 hour charged
}
private double getRateForSize(String size) {
switch (size) {
case "small":
return 2.5;
case "medium":
return 3.5;
case "large":
return 5.0;
default:
throw new IllegalArgumentException("Invalid spot size");
}
}
}
public class Command {
private LocalDateTime updatedTime = LocalDateTime.now();
public void assign(String spotId, String vehicleId, String userId) {
String sql = "UPDATE ParkingSpot SET availability = 0, lastUpdate = ?, vehicleId = ? WHERE spotId = ?";
String sql = "UPDATE User SET lastUpdate = ? WHERE userId = ?";
try {
System.out.println("Assign updated in database for spot: " + spotId);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void checkin(String spotId, String vehicleId, String userId) {
this.vehicleId = vehicleId;
this.userId = vehicleId.getUserId();
String sql = "UPDATE ParkingSpot SET availability = -1, vehicleId = ?, lastUpdate = ? WHERE spotId = ?";
String sql = "UPDATE User SET lastUpdate = ? WHERE userId = ?";
try {
System.out.println("Check-in updated in database for spot: " + spotId);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void checkout(String spotId, String userId) {
String sql = "UPDATE ParkingSpot SET availability = 1, vehicleId = NULL, lastUpdate = ? WHERE spotId = ?";
String sql = "UPDATE User SET lastUpdate = ? WHERE userId = ?";
try {
System.out.println("Check-out updated in database for spot: " + spotId);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void updateAvailability(String spotId, int availability) {
String sql = "UPDATE ParkingSpot SET availability = ?, lastUpdate = ? WHERE spotId = ?";
try {
System.out.println("Availability updated in database for spot: " + spotId);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public class PaymentService {
public Payment createPayment(String spotId, String spotSize, LocalDateTime checkIn, LocalDateTime checkoutTime, double unitPrice) {
Payment payment = new Payment(spotId, spotSize, checkIn, checkoutTime, unitPrice);
saveToDatabase(payment);
return payment;
}
public void processPayment(Payment payment, String transactionId) {
payment.setTransactionId(transactionId);
payment.setPaymentStatus(true);
updateStatus(payment);
}
public void updateStatus(Payment payment) {
String sql = "UPDATE Payment SET transactionId = ?, paymentStatus = ?, checkOutTime = ?, updatedTime = ? WHERE paymentId = ?";
try {
System.out.println("Payment status updated in database for payment ID: " + payment.getPaymentId());
} catch (SQLException e) {
e.printStackTrace();
}
}
private void saveToDatabase(Payment payment) {
String sql = "INSERT INTO Payment (paymentId, spotId, spotSize, checkInTime, expiredTime, unitPrice, amount, paymentStatus, createdTime, updatedTime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
try {
System.out.println("Payment saved to database: " + payment.getPaymentId());
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Check and explain whether your design adheres to solid principles (Ask interviewer what SOLID principle is if you can not recall it.)...
S: single responsibility: each class should have standalone responsibility
O: open/closed principle: if i need new parking strategy, i can add a standalone strategy class to assign the strategy, and if i need to change fee calculation, i can change the fee calculation strategy class
L: subclass cannot intervene top class: all payment related operations are handled within payment service
I: Interface segregation principle: my payment system handles creating processing payment and update status; fee class handles fee calculation; and command class handles assign check in and checkout parking spot and update availbility of the spot; and user and vehicle factory create and delete user; so they only contains useful functions
D: yea i have the high level and low level module
Explain how your design can handle changes in scale and whether it would be easily to extend with new functionalities...
check my high level diagram for service granulility, load balancing and cache service
I use redis to fetch a list of different sizes of parking lots, so the system can repsond to parking lot assignment in 500 ms even during peek time.
if i need to renovate the parking lot, i can add new spot to the parking spot db with new floor number, size or others
the spot assignment, fee caulcation and payment are standalone features, so if its in high traffic, i can scale horizontally to match the need
the alert system is handled through microservice, so if there are more need, we can scale if separately
i have the logging service for health check and analytics so i can increase the instance based on recent activity load
Try creating a class, flow, state and/or sequence diagram using the diagramming tool. Mermaid flow diagrams can be used to represent system use cases. You can ask the interviewer bot to create a starter diagram if unfamiliar with the tool. Briefly explain your diagrams if necessary...
i will use redis to reserve the spot and have it ttl of 5 min,
and if the client checkout, also hold a 5 min ttl to change it back to available
the db are sharded on diff db, so if part of the spot isnt available, my payment class can still use spot size and do the calculation, to correctly know the spot size even if the spot id isnt accessible, i will add the size identifer to the start of the spot id
Critically examine your design for any flaws or areas for future improvement...