A ride matching REST API built with Spring Boot. Allocates available drivers to ride requests based on proximity, with safe concurrent handling.
- Java 17
- No database or external services needed
./gradlew bootRunThe server starts on http://localhost:8080.
./gradlew buildPOST /drivers
{
"name": "John Doe",
"phoneNumber": "+35699123456",
"vehicleType": "SEDAN"
}POST /drivers/availability
{
"driverId": "<uuid>",
"latitude": 35.8997,
"longitude": 14.5147
}Returns 409 if the driver currently has an active ride.
POST /rides
{
"riderId": "<uuid>",
"pickupLatitude": 35.9010,
"pickupLongitude": 14.5130
}Returns the ride details along with the allocated driver. Returns 404 if no drivers are available.
PUT /rides/{rideId}/complete
Returns 404 if the ride does not exist, 409 if already completed.
GET /drivers/available?latitude=35.90&longitude=14.51&limit=5
Returns a list of available drivers sorted by ascending distance from the given location.
All data is stored in-memory using ConcurrentHashMap. There is no database. Data does not persist between application restarts.
Three repositories hold the state:
DriverRepository- all registered driversDriverAvailabilityRepository- driver availability records and claim slotsRideRepository- all rides
DriverAvailabilityRepository uses two maps:
- primaryMap (keyed by availability id) - permanent record of a driver's last registered location. Never removed from.
- claimMap (keyed by driverId) - the live availability signal. An entry present means the driver is free. An entry absent means the driver is in a ride.
When a driver registers availability, they are added to both maps. When allocated to a ride, they are removed from the claimMap only. When the ride completes, they are re-inserted into the claimMap from the primaryMap.
When a ride is requested:
- All drivers in the claimMap are fetched and filtered (excluding any with an active ride as a safety check).
- Haversine distance is calculated from each driver's registered location to the rider's pickup location.
- Drivers are sorted by ascending distance.
- The service iterates the sorted list and attempts to atomically claim each driver using
ConcurrentHashMap.remove(key, expectedValue). - Only one thread can successfully remove a given entry. If the claim fails (another thread got there first), the next nearest driver is tried.
- Once a driver is claimed, a
Riderecord is created with statusACTIVEand returned to the rider along with the driver's details.
Marking a ride complete sets its status to COMPLETED, records the completion timestamp, and calls release() on the availability repository, which re-inserts the driver into the claimMap making them available for new requests.
This is a small-scale project built for clarity and correctness over scalability. More advanced approaches were intentionally left out:
- No GeoHashing or spatial indexing - driver distance is computed by iterating all available drivers and applying the Haversine formula. This is sufficient at small scale but would not hold up with thousands of concurrent drivers. A production system would use a spatial index (e.g. GeoHash, R-tree, PostGIS) to query only nearby drivers.
- No persistent storage - in-memory maps reset on restart. A real system would use a database.
- No authentication - riderIds and driverIds are caller-supplied UUIDs with no validation of identity.