Requirements & System Scope
Functional Scope (In-Scope)
- Physical IoT Unlock Cycles: Validates bike states atomically, triggers virtual hardware lock controllers, and tracks trip beginnings.
- Hybrid Dock/Dockless Parking Validators: Enforces returns within either physical station slots or virtual geofenced circles.
- Adaptive Per-Minute Billing Engine: Calculates trip cost base rates, running duration minutes, and caps charges at a daily maximum.
- Stateful Trip Management: Restricts users to one active bike booking at a time and logs completed coordinates.
Explicit Boundaries (Out-of-Scope)
- Dynamic Maintenance Dispatching: Focuses on on-trip state changes rather than rebalancing truck routing algorithms.
- Real-time GPS Tracking Channels: Excludes handling WebSockets or telemetry data feeds to focus on start/end coordinate validation.
Class Diagram & Entity Relationships
IoT triggers, trip states, geofence zones, and billing structures:
Loading...
- Bike & Dock: Tracks location arrays and physical slots.
- Trip: Logs coordinates, timestamps, and fees.
- LockController: Connects core servers to hardware IoT locks.
Design Patterns & SOLID Principles
- State Machine Pattern: Governs bike lifecycle transitions (Available, In Use, Maintenance) to prevent illegal double-booking operations.
- Proxy Pattern (IoT Hardware Proxy): Simulates hardware interactions using a dedicated mock lock controller, decoupled from core business services.
- Single Responsibility Principle (SRP): Isolates fare calculations, physical geofence validations, IoT command routing, and trip session storage.
Core Execution Workflows
Booking, Lock Sequences, and Parking Enforcement
- Lock/Unlock IoT Workflows:
- Verify if the target bike status is `AVAILABLE` and update it to `IN_USE` using comparison checks.
- Dispatch the unlock signal to the
LockController. If the signal fails, roll back the status update. - If the bike is docked, decrement the dock's occupied slots and set the bike's current dock reference to null.
- Trip Conclution & Billing Calculations:
- Upon receiving a return signal, lock the bike using the
LockController. - Check if the user is parked at a physical dock or inside a geofenced area. If neither, log an out-of-bounds warning.
- Calculate the trip duration:
duration = now - start. Apply billing rules and transition the bike back to `AVAILABLE`.
- Upon receiving a return signal, lock the bike using the
Concurrency & Thread Safety Strategy
Securing parallel bookings and dock tallies under high concurrent demand:
- Atomic CAS Transitions: Uses comparison check operations to update bike statuses, ensuring a bike can never be rented by multiple users simultaneously.
- Synchronized Dock Counters: Synchronizes slot modifications to prevent double-allocation errors when multiple riders return bikes to the same dock.
Complete Clean Code Blueprint
Production reference implementations demonstrating IoT lock integrations, geofenced boundaries, and duration billing calculators in Java and Python:
// โโโ JAVA BLUEPRINT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
enum BikeStatus {
AVAILABLE, IN_USE, MAINTENANCE
}
enum TripStatus {
ACTIVE, COMPLETED, UNPAID
}
class Point {
private final double lat;
private final double lng;
public Point(double lat, double lng) {
this.lat = lat;
this.lng = lng;
}
public double getLat() { return lat; }
public double getLng() { return lng; }
public double distanceTo(Point other) {
double R = 6371000.0; // Earth's radius in meters
double latDist = Math.toRadians(other.lat - this.lat);
double lngDist = Math.toRadians(other.lng - this.lng);
double a = Math.sin(latDist / 2) * Math.sin(latDist / 2)
+ Math.cos(Math.toRadians(this.lat)) * Math.cos(Math.toRadians(other.lat))
* Math.sin(lngDist / 2) * Math.sin(lngDist / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
}
}
class Bike {
private final String bikeId;
private final AtomicReference<BikeStatus> status = new AtomicReference<>(BikeStatus.AVAILABLE);
private volatile Point location;
private volatile String currentDockId = null;
public Bike(String bikeId, Point initialLocation) {
this.bikeId = bikeId;
this.location = initialLocation;
}
public String getBikeId() { return bikeId; }
public BikeStatus getStatus() { return status.get(); }
public boolean compareAndSetStatus(BikeStatus expect, BikeStatus update) {
return status.compareAndSet(expect, update);
}
public void setStatus(BikeStatus s) { status.set(s); }
public Point getLocation() { return location; }
public void setLocation(Point location) { this.location = location; }
public String getCurrentDockId() { return currentDockId; }
public void setCurrentDockId(String currentDockId) { this.currentDockId = currentDockId; }
}
class Dock {
private final String dockId;
private final Point location;
private final int capacity;
private int occupiedSlots = 0;
public Dock(String dockId, Point location, int capacity) {
this.dockId = dockId;
this.location = location;
this.capacity = capacity;
}
public synchronized boolean hasAvailableSlots() {
return occupiedSlots < capacity;
}
public synchronized boolean incrementOccupied() {
if (occupiedSlots < capacity) {
occupiedSlots++;
return true;
}
return false;
}
public synchronized void decrementOccupied() {
if (occupiedSlots > 0) {
occupiedSlots--;
}
}
public String getDockId() { return dockId; }
public Point getLocation() { return location; }
public int getCapacity() { return capacity; }
public synchronized int getOccupiedSlots() { return occupiedSlots; }
}
class ParkingZone {
private final String zoneId;
private final Point center;
private final double radiusMeters;
public ParkingZone(String zoneId, Point center, double radiusMeters) {
this.zoneId = zoneId;
this.center = center;
this.radiusMeters = radiusMeters;
}
public boolean contains(Point point) {
return center.distanceTo(point) <= radiusMeters;
}
public String getZoneId() { return zoneId; }
}
class Trip {
private final String tripId;
private final String userId;
private final String bikeId;
private final long startTimeMs;
private final Point startLocation;
private volatile long endTimeMs = 0L;
private volatile Point endLocation = null;
private volatile double fare = 0.0;
private volatile TripStatus status = TripStatus.ACTIVE;
public Trip(String tripId, String userId, String bikeId, Point startLocation) {
this.tripId = tripId;
this.userId = userId;
this.bikeId = bikeId;
this.startTimeMs = System.currentTimeMillis();
this.startLocation = startLocation;
}
public void completeTrip(Point endLocation, double fare) {
this.endTimeMs = System.currentTimeMillis();
this.endLocation = endLocation;
this.fare = fare;
this.status = TripStatus.COMPLETED;
}
public String getTripId() { return tripId; }
public String getUserId() { return userId; }
public String getBikeId() { return bikeId; }
public long getStartTimeMs() { return startTimeMs; }
public Point getStartLocation() { return startLocation; }
public long getEndTimeMs() { return endTimeMs; }
public Point getEndLocation() { return endLocation; }
public double getFare() { return fare; }
public TripStatus getStatus() { return status; }
}
class BillingEngine {
private static final double BASE_FEE = 2.0;
private static final double RATE_PER_MINUTE = 0.15;
private static final double DAILY_MAX_CAP = 20.0;
public static double calculateFare(long startTimeMs, long endTimeMs) {
long durationMs = endTimeMs - startTimeMs;
if (durationMs < 0) durationMs = 0;
double durationMinutes = durationMs / 60000.0;
double charge = BASE_FEE + (durationMinutes * RATE_PER_MINUTE);
return Math.min(charge, DAILY_MAX_CAP);
}
}
class LockController {
// Simulates an IoT Lock gateway unlock command
public boolean sendUnlockSignal(String bikeId) {
System.out.println("IOT SIGNAL -> Unlocking Bike: " + bikeId);
return true; // Return successful hardware feedback
}
public boolean sendLockSignal(String bikeId) {
System.out.println("IOT SIGNAL -> Locking Bike: " + bikeId);
return true;
}
}
class BikeSharingService {
private final ConcurrentHashMap<String, Bike> bikes = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Dock> docks = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, ParkingZone> geofences = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Trip> activeTrips = new ConcurrentHashMap<>(); // userId -> Trip
private final ConcurrentHashMap<String, List<Trip>> userHistory = new ConcurrentHashMap<>();
private final LockController lockController = new LockController();
public void registerBike(Bike bike) {
bikes.put(bike.getBikeId(), bike);
}
public void registerDock(Dock dock) {
docks.put(dock.getDockId(), dock);
}
public void registerParkingZone(ParkingZone zone) {
geofences.put(zone.getZoneId(), zone);
}
// Dynamic Unlock Sequence
public Trip unlockBike(String userId, String bikeId) {
Bike bike = bikes.get(bikeId);
if (bike == null) throw new IllegalArgumentException("Bike does not exist!");
if (activeTrips.containsKey(userId)) {
throw new IllegalStateException("User already has an active trip!");
}
// Atomically transitions bike state
if (!bike.compareAndSetStatus(BikeStatus.AVAILABLE, BikeStatus.IN_USE)) {
throw new IllegalStateException("Bike is not available for booking!");
}
// Dispatch hardware signal
boolean hardwareSuccess = lockController.sendUnlockSignal(bikeId);
if (!hardwareSuccess) {
bike.setStatus(BikeStatus.AVAILABLE); // Rollback state
throw new RuntimeException("Hardware failed to unlock bike!");
}
// Release occupied slot if bike was in a dock
if (bike.getCurrentDockId() != null) {
Dock dock = docks.get(bike.getCurrentDockId());
if (dock != null) {
dock.decrementOccupied();
}
bike.setCurrentDockId(null);
}
String tripId = UUID.randomUUID().toString();
Trip trip = new Trip(tripId, userId, bikeId, bike.getLocation());
activeTrips.put(userId, trip);
System.out.println("TRIP STARTED -> User: " + userId + " | Trip: " + tripId + " | Bike: " + bikeId);
return trip;
}
// Dynamic Return & End Trip Sequence
public void lockBike(String userId, Point returnLocation) {
Trip trip = activeTrips.get(userId);
if (trip == null) {
throw new IllegalStateException("No active trip found for user!");
}
String bikeId = trip.getBikeId();
Bike bike = bikes.get(bikeId);
if (bike == null) return;
// Verify IoT lock feedback
boolean hardwareSuccess = lockController.sendLockSignal(bikeId);
if (!hardwareSuccess) {
throw new RuntimeException("Hardware failed to confirm lock status!");
}
// Parking zone verification (Dock or Dockless geofence)
boolean validParking = false;
String dockIdMatched = null;
// 1. Check if parked at a dock
for (Dock dock : docks.values()) {
if (dock.getLocation().distanceTo(returnLocation) <= 15.0) { // Within 15 meters
if (dock.incrementOccupied()) {
validParking = true;
dockIdMatched = dock.getDockId();
break;
}
}
}
// 2. Check if parked inside a dockless geofence zone
if (!validParking) {
for (ParkingZone zone : geofences.values()) {
if (zone.contains(returnLocation)) {
validParking = true;
break;
}
}
}
if (!validParking) {
System.out.println("OUT-OF-BOUNDS WARNING -> User parked outside permitted zone. Applying surcharge penalty!");
// Surcharges could be applied to fare calculations here
}
// Finalize state updates
long now = System.currentTimeMillis();
double fare = BillingEngine.calculateFare(trip.getStartTimeMs(), now);
trip.completeTrip(returnLocation, fare);
bike.setLocation(returnLocation);
bike.setCurrentDockId(dockIdMatched);
bike.setStatus(BikeStatus.AVAILABLE);
activeTrips.remove(userId);
userHistory.computeIfAbsent(userId, k -> new CopyOnWriteArrayList<>()).add(trip);
System.out.println("TRIP CONCLUDED -> User: " + userId + " | Fare: $" + fare + " | Parked Dock: " + (dockIdMatched != null ? dockIdMatched : "Dockless Zone"));
}
public List<Trip> getUserHistory(String userId) {
return userHistory.getOrDefault(userId, Collections.emptyList());
}
}
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("=== JAVA BIKE SHARING SYSTEM SIMULATION ===");
BikeSharingService service = new BikeSharingService();
Point startLoc = new Point(40.7128, -74.0060);
Bike bike1 = new Bike("bike-101", startLoc);
service.registerBike(bike1);
Dock dock1 = new Dock("dock-001", new Point(40.7129, -74.0061), 5);
dock1.incrementOccupied(); // bike1 is in this dock initially
bike1.setCurrentDockId("dock-001");
service.registerDock(dock1);
ParkingZone zone1 = new ParkingZone("zone-south", new Point(40.7100, -74.0090), 100.0);
service.registerParkingZone(zone1);
// Unlock
Trip trip = service.unlockBike("user-1", "bike-101");
Thread.sleep(100); // simulate a tiny delay
// Return location (near the parking zone)
Point endLoc = new Point(40.7102, -74.0091);
service.lockBike("user-1", endLoc);
System.out.println("User trip count: " + service.getUserHistory("user-1").size());
System.out.println("=== END OF JAVA SIMULATION ===");
}
}Review
Help Us Improve
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Placeholder
Discussion
Share your thoughts, ask questions, or help others.
Loading comments...