A driver matching system is a critical component of ride-sharing platforms that efficiently pairs available drivers with passengers based on proximity, availability, and service preferences. This design addresses the core challenges of real-time matching, geospatial indexing, and scalable infrastructure to handle high-volume concurrent requests while maintaining low latency and optimal resource utilization.
1. Problem Statement & Requirements
Clarifying questions
-
What is the scale of the system?
- How many drivers and riders are expected to be onboarded in the first year?
- What is the peak concurrent request rate for matching drivers to riders?
-
What consistency requirements exist?
- Is real-time matching required, or can there be a slight delay (e.g., 1-2 seconds)?
- Should matching guarantees be strong (e.g., no duplicate matches) or eventual consistency acceptable?
-
What are the key entities and their relationships?
- Are there different driver types (e.g., taxi, ride-share, motorcycle)?
- How are rider and driver locations updated — manually or through GPS?
-
What is the read/write ratio for matching operations?
- Is it predominantly driver registration and location updates, or mostly matching requests?
-
What are the latency sensitivity requirements?
- What is the maximum acceptable time for a match to be returned (e.g., <50ms, <200ms)?
-
What authentication and authorization model is in place?
- Are drivers and riders authenticated via OAuth2 or JWT tokens?
- Is there a role-based access control (RBAC) system?
-
Are there external integrations or dependencies?
- Does the system integrate with third-party mapping services (e.g., Google Maps, Mapbox) for distance calculation?
- Is there a need to support ride sharing or group bookings?
Functional requirements
-
Driver registration and profile management
- Drivers can register with their details (name, license, vehicle type, etc.)
- Drivers can update their availability status and location
-
Rider ride request submission
- Riders submit ride requests with pickup and destination locations
-
Real-time driver matching
- System matches riders to nearby available drivers based on location, vehicle type, and availability
-
Match confirmation and notification
- Upon match, both rider and driver receive notifications with details
-
Match cancellation and re-matching
- Riders or drivers can cancel matches, triggering re-matching for the rider
Non-functional requirements
-
Latency targets
- p50: <100ms
- p95: <200ms
- p99: <500ms
-
Throughput
- 10,000 concurrent ride requests per second (with peak bursts up to 25,000)
-
Availability
- 99.9% uptime (5-minute SLA)
- Support for graceful degradation during partial outages
Constraints and assumptions
-
Assumptions
- Driver and rider locations are updated every 5–10 seconds via mobile app
- Drivers must be within a 5km radius of the rider to qualify for matching
- Matched drivers and riders are expected to be in proximity (within 100m) for pickup
-
Constraints
- System must support matching within a 5km radius
- No external dependencies for core matching logic (e.g., no third-party geolocation services)
- Matching must be performed in real-time or near-real-time (max 2 seconds)
Out of scope
-
Geofencing and zone-based matching
- Matching based on predefined zones or regions is not part of this design
-
Ride history and analytics dashboard
- Historical ride data and performance dashboards are out of scope
-
Payment processing integration
- Payment flow and transaction handling are not part of this system
-
Multi-language or internationalization support
- The system is designed for a single locale and language
-
Driver rating and feedback system
- Post-ride ratings or reviews are not included in this design
2. API Design
API Style Selection
For a driver matching system, I'll select gRPC as the primary API style with REST fallback for client compatibility.
Justification:
- gRPC provides efficient binary serialization, strong typing, and built-in support for streaming
- Better performance for real-time matching operations with low latency requirements
- Native support for bidirectional streaming (e.g., real-time driver location updates)
- Strong contract definition with Protocol Buffers enables better tooling and versioning
- REST fallback allows for browser-based client integration and simpler debugging
Endpoint Definitions
| Method | Path | Description | Auth | Rate Limit |
|---|---|---|---|---|
| POST | /v1/driver/requests | Submit driver location and availability | JWT | 100 req/min |
| GET | /v1/driver/requests/{id} | Get driver request status | JWT | 50 req/min |
| POST | /v1/driver/requests/{id}/accept | Accept driver request | JWT | 50 req/min |
| POST | /v1/driver/requests/{id}/reject | Reject driver request | JWT | 50 req/min |
| POST | /v1/driver/requests/{id}/update | Update driver location | JWT | 100 req/min |
| GET | /v1/driver/requests/{id}/status | Get request status | JWT | 50 req/min |
| POST | /v1/driver/requests/{id}/complete | Complete driver request | JWT | 50 req/min |
| GET | /v1/drivers/nearby | Find nearby drivers | JWT | 50 req/min |
| GET | /v1/drivers/{id}/profile | Get driver profile | JWT | 50 req/min |
Request and Response Schemas
Driver Location Update Request
{
"driverId": "uuid",
"latitude": 40.7128,
"longitude": -74.0060,
"timestamp": "2023-12-01T10:00:00Z",
"accuracy": 5.0
}
Driver Request Response
{
"id": "uuid",
"driverId": "uuid",
"status": "PENDING|ACCEPTED|REJECTED|COMPLETED",
"origin": {
"latitude": 40.7128,
"longitude": -74.0060
},
"destination": {
"latitude": 40.7589,
"longitude": -73.9851
},
"createdAt": "2023-12-01T10:00:00Z",
"updatedAt": "2023-12-01T10:00:00Z"
}
Nearby Drivers Response
{
"drivers": [
{
"id": "uuid",
"latitude": 40.7128,
"longitude": -74.0060,
"distanceMeters": 1200,
"rating": 4.8,
"available": true
}
],
"count": 5
}
HTTP Status Codes and Error Response
Standard HTTP Status Codes:
- 200 OK - Successful GET, PUT, PATCH, DELETE
- 201 Created - Successful POST (resource created)
- 400 Bad Request - Invalid request parameters
- 401 Unauthorized - Authentication required or invalid token
- 403 Forbidden - Insufficient permissions
- 404 Not Found - Resource not found
- 429 Too Many Requests - Rate limit exceeded
- 500 Internal Server Error - Server error
Standardized Error Response Format:
{
"error": {
"code": "DRIVER_NOT_FOUND",
"message": "Driver with ID abc-123 not found",
"details": {
"driverId": "abc-123"
},
"timestamp": "2023-12-01T10:00:00Z"
}
}
API Versioning Strategy
Versioning will be done via path prefix: /v1/ with future versions as /v2/, etc. This approach provides:
- Clear separation between major versions
- Backward compatibility for existing clients
- Easy migration path for clients
Pagination Design
Cursor-based pagination will be used for the /drivers/nearby endpoint.
Justification:
- More stable than offset-based pagination when data changes
- Better performance for large datasets
- Consistent results even with concurrent updates to the dataset
Authentication and Authorization
Authentication:
- JWT tokens for API authentication
- OAuth2 with refresh token support for mobile apps
Authorization:
- JWT claims will contain role information (driver, admin)
- Driver-specific endpoints require driver role
- Admin endpoints require admin role
Rate Limiting Strategy
Rate limiting will be implemented at the service layer using a token bucket algorithm with:
- Per-user rate limits (100 req/min for location updates, 50 req/min for other operations)
- Global rate limits (1000 req/sec for public endpoints)
- Sliding window implementation to prevent burst attacks
- Distributed rate limiting using Redis for multi-instance deployments
Idempotency Design
For mutation endpoints, idempotency keys will be supported:
// Example usage in gRPC service
public class DriverRequestService {
public void updateDriverLocation(UpdateLocationRequest request, String idempotencyKey) {
// Check if this operation was already processed
if (isIdempotentOperationProcessed(idempotencyKey)) {
return; // No-op
}
// Process the operation
processLocationUpdate(request);
// Mark operation as processed
markIdempotentOperationProcessed(idempotencyKey);
}
}
Implementation:
- Idempotency keys will be stored in Redis with TTL of 5 minutes
- Keys are generated from client-provided unique identifiers or request signatures
- For gRPC, clients must provide the
Idempotency-Keyheader - For REST, clients can use the
X-Idempotency-Keyheader
3. Data Model Design
Entity-Relationship Diagram
erDiagram
DRIVER {
string id PK
string userId FK
string firstName
string lastName
string email
string phone
string licenseNumber
string vehicleType
string status
timestamp createdAt
timestamp updatedAt
timestamp lastActiveAt
}
USER {
string id PK
string email
string passwordHash
string role
timestamp createdAt
timestamp updatedAt
}
DRIVER_LOCATION {
string id PK
string driverId FK
float latitude
float longitude
timestamp recordedAt
string status
}
DRIVER_VEHICLE {
string id PK
string driverId FK
string make
string model
string year
string licensePlate
timestamp createdAt
timestamp updatedAt
}
DRIVER_RATING {
string id PK
string driverId FK
float rating
int count
timestamp createdAt
timestamp updatedAt
}
DRIVER_AVAILABILITY {
string id PK
string driverId FK
timestamp startTime
timestamp endTime
string status
timestamp createdAt
timestamp updatedAt
}
DRIVER_MATCH {
string id PK
string driverId FK
string tripId FK
string status
timestamp matchedAt
timestamp updatedAt
}
DRIVER {
string userId FK --> USER.id
}
DRIVER_LOCATION {
string driverId FK --> DRIVER.id
}
DRIVER_VEHICLE {
string driverId FK --> DRIVER.id
}
DRIVER_RATING {
string driverId FK --> DRIVER.id
}
DRIVER_AVAILABILITY {
string driverId FK --> DRIVER.id
}
DRIVER_MATCH {
string driverId FK --> DRIVER.id
string tripId FK --> TRIP.id
}
Table Schema Definitions
-- Drivers table
CREATE TABLE drivers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
phone VARCHAR(20),
license_number VARCHAR(100) UNIQUE NOT NULL,
vehicle_type VARCHAR(50),
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Driver locations table
CREATE TABLE driver_locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
driver_id UUID NOT NULL REFERENCES drivers(id) ON DELETE CASCADE,
latitude FLOAT NOT NULL,
longitude FLOAT NOT NULL,
recorded_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
INDEX idx_driver_location_time (driver_id, recorded_at),
INDEX idx_location_coordinates (latitude, longitude)
);
-- Driver vehicles table
CREATE TABLE driver_vehicles (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
driver_id UUID NOT NULL REFERENCES drivers(id) ON DELETE CASCADE,
make VARCHAR(100) NOT NULL,
model VARCHAR(100) NOT NULL,
year INTEGER NOT NULL,
license_plate VARCHAR(50) UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Driver ratings table
CREATE TABLE driver_ratings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
driver_id UUID NOT NULL REFERENCES drivers(id) ON DELETE CASCADE,
rating FLOAT NOT NULL CHECK (rating >= 0 AND rating <= 5),
count INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Driver availability table
CREATE TABLE driver_availability (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
driver_id UUID NOT NULL REFERENCES drivers(id) ON DELETE CASCADE,
start_time TIMESTAMP NOT NULL,
end_time TIMESTAMP NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_driver_availability_time (driver_id, start_time, end_time),
INDEX idx_driver_availability_status (driver_id, status)
);
-- Driver matches table
CREATE TABLE driver_matches (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
driver_id UUID NOT NULL REFERENCES drivers(id) ON DELETE CASCADE,
trip_id UUID NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
matched_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Primary Key Strategy
UUIDs are used as primary keys for all tables due to:
- Distributed system compatibility: UUIDs ensure uniqueness across distributed systems without coordination
- No sequential patterns: Prevents predictability and potential security concerns
- Scalability: No need for centralized ID generation, avoiding bottlenecks
- Cross-system integration: Easier to merge data from different systems
Index Design
| Index Name | Fields | Type | Purpose |
|---|---|---|---|
idx_driver_location_time | driver_id, recorded_at | Composite | Efficient location history queries |
idx_location_coordinates | latitude, longitude | Spatial | Fast geospatial queries for matching |
idx_driver_availability_time | driver_id, start_time, end_time | Composite | Fast availability window queries |
idx_driver_availability_status | driver_id, status | Composite | Efficient availability status lookups |
idx_driver_matches_trip | trip_id | Single field | Fast lookup by trip ID |
idx_drivers_user_id | user_id | Single field | Fast lookup by user ID |
idx_drivers_status | status | Single field | Fast filtering by driver status |
Data Partitioning / Sharding Strategy
Geographic sharding based on driver location:
-
Partition by latitude/longitude ranges:
- North America: 25°N-50°N, -125°W--65°W
- Europe: 35°N-70°N, -10°W-40°E
- Asia: 5°N-55°N, 60°E-180°E
-
Shard key:
latitudeandlongituderanges with a hash-based sharding approach -
Cross-shard queries: Handled through coordinator service that routes to appropriate shards
Soft Delete vs. Hard Delete Decision
Soft delete is chosen because:
- Audit trail requirement: Need to maintain historical match data for analytics and dispute resolution
- Compliance: Regulatory requirements may mandate data retention
- Data integrity: Prevents orphaned references in related tables
- Recovery capability: Ability to restore deleted drivers or matches if needed
Audit Trail Design
All tables include created_at and updated_at timestamps. Additionally:
- Change tracking table (not shown) that logs:
operation_type(INSERT/UPDATE/DELETE)changed_fields(JSON array of modified fields)old_valuesandnew_values(JSONB for complex objects)user_id(if available)timestamp
Data Archival Strategy and Retention Policy
-
Driver match history:
- Keep 2 years of active match data
- Archive to cold storage (S3 Glacier) after 2 years
-
Location history:
- Keep 30 days of location data for matching
- Archive to cold storage after 30 days
-
Driver status changes:
- Keep 5 years of status change history
- Archive to data warehouse for analytics
-
Archival process:
- Scheduled nightly job to move old data
- Maintains referential integrity through soft deletes
- Updates audit trail with archival metadata
4. Class Design & Service Layer
classDiagram
class Driver {
+String id
+String name
+String licenseNumber
+String phoneNumber
+Location currentLocation
+boolean isAvailable
+double rating
+int totalRides
+Date lastActive
+List~Vehicle~ vehicles
+void updateLocation(Location location)
+void markAvailable()
+void markUnavailable()
}
class Vehicle {
+String id
+String make
+String model
+String licensePlate
+String color
+int seatingCapacity
+boolean isAvailable
+String driverId
}
class Location {
+double latitude
+double longitude
+String address
+Date timestamp
}
class RideRequest {
+String id
+String customerId
+Location pickupLocation
+Location dropoffLocation
+RideStatus status
+Date requestedAt
+Date matchedAt
+String driverId
+double estimatedFare
}
class RideStatus {
PENDING
MATCHED
IN_PROGRESS
COMPLETED
CANCELLED
}
class DriverMatchingService {
+List~Driver~ findAvailableDrivers(Location pickupLocation, int radius)
+Driver matchDriver(RideRequest rideRequest)
+void updateDriverAvailability(String driverId, boolean available)
+void processRideCompletion(String rideId)
}
class RideRequestRepository {
+RideRequest save(RideRequest request)
+RideRequest findById(String id)
+void updateStatus(String id, RideStatus status)
}
class DriverRepository {
+Driver save(Driver driver)
+Driver findById(String id)
+List~Driver~ findAvailableWithinRadius(Location center, int radius)
+void updateAvailability(String id, boolean available)
}
class LocationService {
+double calculateDistance(Location loc1, Location loc2)
+boolean isWithinRadius(Location center, Location point, int radius)
}
class EventPublisher {
+void publishRideMatchedEvent(RideRequest request, Driver driver)
+void publishRideCompletedEvent(String rideId)
}
class DriverMatchingStrategy {
+Driver selectBestDriver(List~Driver~ candidates, RideRequest request)
}
class DistanceBasedMatchingStrategy {
+Driver selectBestDriver(List~Driver~ candidates, RideRequest request)
}
Driver --> Vehicle : has
Driver --> Location : currentLocation
RideRequest --> Location : pickupLocation, dropoffLocation
DriverMatchingService --> DriverRepository : data access
DriverMatchingService --> RideRequestRepository : data access
DriverMatchingService --> LocationService : location logic
DriverMatchingService --> EventPublisher : event publishing
DriverMatchingService --> DriverMatchingStrategy : matching logic
DriverMatchingStrategy <|-- DistanceBasedMatchingStrategy : implements
stateDiagram-v2
[*] --> PENDING
PENDING --> MATCHED : Driver matched
MATCHED --> IN_PROGRESS : Ride started
IN_PROGRESS --> COMPLETED : Ride finished
PENDING --> CANCELLED : Request cancelled
MATCHED --> CANCELLED : Driver unavailable
IN_PROGRESS --> CANCELLED : Ride cancelled
Service Class Structure and Responsibilities
The Driver Matching system consists of several key service classes:
- DriverMatchingService - Main orchestration service for matching drivers to ride requests
- RideRequestRepository - Data access layer for ride request entities
- DriverRepository - Data access layer for driver entities
- LocationService - Geospatial calculation utilities
- EventPublisher - Event publishing for system notifications
Interface / Abstract Class Design
public interface DriverMatchingStrategy {
Driver selectBestDriver(List<Driver> candidates, RideRequest request);
}
public abstract class AbstractDriverMatchingService {
protected final DriverRepository driverRepository;
protected final RideRequestRepository rideRequestRepository;
protected final LocationService locationService;
protected final EventPublisher eventPublisher;
protected final DriverMatchingStrategy matchingStrategy;
public AbstractDriverMatchingService(DriverRepository driverRepository,
RideRequestRepository rideRequestRepository,
LocationService locationService,
EventPublisher eventPublisher,
DriverMatchingStrategy matchingStrategy) {
this.driverRepository = driverRepository;
this.rideRequestRepository = rideRequestRepository;
this.locationService = locationService;
this.eventPublisher = eventPublisher;
this.matchingStrategy = matchingStrategy;
}
public abstract Driver matchDriver(RideRequest rideRequest);
}
Business Logic Encapsulation Patterns
Strategy Pattern Implementation:
public class DistanceBasedMatchingStrategy implements DriverMatchingStrategy {
private static final int MAX_RADIUS_KM = 10;
@Override
public Driver selectBestDriver(List<Driver> candidates, RideRequest request) {
if (candidates.isEmpty()) {
return null;
}
return candidates.stream()
.filter(Driver::isAvailable)
.min(Comparator.comparing(driver ->
calculateDriverScore(driver, request)))
.orElse(null);
}
private double calculateDriverScore(Driver driver, RideRequest request) {
double distance = locationService.calculateDistance(
driver.getCurrentLocation(),
request.getPickupLocation()
);
// Prefer drivers closer to pickup location
return distance;
}
}
Critical-Path Method Implementation
public Driver matchDriver(RideRequest rideRequest) {
// Transaction boundary - all operations in single transaction
return transactionTemplate.execute(status -> {
try {
// 1. Validate ride request is in PENDING state
if (rideRequest.getStatus() != RideStatus.PENDING) {
throw new IllegalStateException("Ride request must be in PENDING state");
}
// 2. Find available drivers within radius
List<Driver> availableDrivers = driverRepository.findAvailableWithinRadius(
rideRequest.getPickupLocation(),
MAX_RADIUS_KM
);
if (availableDrivers.isEmpty()) {
throw new NoAvailableDriverException("No drivers available within radius");
}
// 3. Apply matching strategy to select best driver
Driver selectedDriver = matchingStrategy.selectBestDriver(
availableDrivers,
rideRequest
);
if (selectedDriver == null) {
throw new NoAvailableDriverException("No suitable driver found");
}
// 4. Update driver availability to prevent race conditions
driverRepository.updateAvailability(selectedDriver.getId(), false);
// 5. Update ride request with matched driver
rideRequest.setDriverId(selectedDriver.getId());
rideRequest.setStatus(RideStatus.MATCHED);
rideRequest.setMatchedAt(new Date());
// 6. Save updated entities
rideRequestRepository.save(rideRequest);
// 7. Publish event for async processing
eventPublisher.publishRideMatchedEvent(rideRequest, selectedDriver);
return selectedDriver;
} catch (Exception e) {
// Rollback transaction on failure
status.setRollbackOnly();
throw e;
}
});
}
Transaction Boundary Design
All driver matching operations are wrapped in single database transactions to ensure atomicity:
- Find available drivers within radius
- Update driver availability status (preventing race conditions)
- Update ride request state and assign driver
- Publish matching event
Service Interface Contracts
public interface DriverMatchingService {
Driver matchDriver(RideRequest rideRequest);
void updateDriverAvailability(String driverId, boolean available);
void processRideCompletion(String rideId);
List<Driver> findAvailableDrivers(Location pickupLocation, int radius);
}
Domain Object / Value Object Design
public class Driver {
private String id;
private String name;
private String licenseNumber;
private String phoneNumber;
private Location currentLocation;
private boolean isAvailable;
private double rating;
private int totalRides;
private Date lastActive;
private List<Vehicle> vehicles;
// Getters and setters
}
public class Location {
private double latitude;
private double longitude;
private String address;
private Date timestamp;
// Getters and setters
}
public class RideRequest {
private String id;
private String customerId;
private Location pickupLocation;
private Location dropoffLocation;
private RideStatus status;
private Date requestedAt;
private Date matchedAt;
private String driverId;
private double estimatedFare;
// Getters and setters
}
5. Concurrency & Thread Safety
Concurrency Model
The driver matching system will use a thread pool-based concurrency model with Java's ExecutorService and CompletableFuture for asynchronous operations. The system will leverage the event loop pattern for handling concurrent requests, particularly for real-time matching operations and asynchronous notifications.
Race Condition Prevention Strategy
Race conditions will be prevented through:
- Database-level atomic operations for critical updates (e.g., driver availability status, matching state transitions)
- Optimistic locking using version fields in entities to detect concurrent modifications
- Database transactions for atomic operations across multiple related entities
- Thread-safe collections for in-memory caches and temporary state tracking
Optimistic vs. Pessimistic Locking Selection
Optimistic locking is selected for the driver matching system because:
- The system handles high-concurrency scenarios with frequent read operations
- Write conflicts are rare and typically occur in low-frequency edge cases
- Pessimistic locking would introduce unnecessary latency and reduce system throughput
- The matching engine can handle conflict resolution gracefully with retries
Deadlock Prevention Strategy
Deadlock prevention will be implemented through:
- Fixed ordering of database operations - all operations on related entities follow a consistent order (e.g., always acquire locks in the same sequence)
- Timeout-based lock acquisition - maximum wait time of 500ms for any database locks
- Transaction isolation level set to READ COMMITTED to prevent phantom reads
- Circuit breaker pattern for database operations to prevent cascading failures
Idempotency Implementation for Concurrent Duplicate Requests
Idempotency will be implemented using:
- Idempotency keys in request headers for matching operations (e.g.,
X-Idempotency-Key) - Database-level uniqueness constraints on matching request identifiers
- In-memory cache with TTL for recent operation results to avoid reprocessing
public class DriverMatchingService {
private final Map<String, CompletableFuture<MatchingResult>> pendingRequests = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
public CompletableFuture<MatchingResult> matchDriver(MatchingRequest request) {
String idempotencyKey = request.getIdempotencyKey();
// Check if this operation is already in progress
CompletableFuture<MatchingResult> existing = pendingRequests.get(idempotencyKey);
if (existing != null) {
return existing;
}
CompletableFuture<MatchingResult> future = new CompletableFuture<>();
pendingRequests.put(idempotencyKey, future);
// Schedule cleanup after timeout
scheduler.schedule(() -> pendingRequests.remove(idempotencyKey), 30, TimeUnit.SECONDS);
return future.thenCompose(result -> {
// Process the actual matching logic
try {
MatchingResult result = processMatching(request);
future.complete(result);
return CompletableFuture.completedFuture(result);
} catch (Exception e) {
future.completeExceptionally(e);
throw new RuntimeException(e);
}
});
}
private MatchingResult processMatching(MatchingRequest request) {
// Implementation details...
return new MatchingResult();
}
}
Thread Safety for Critical Components
- Driver availability cache: Uses
ConcurrentHashMapwith atomic operations - Matching queue: Thread-safe queue implementation with bounded capacity
- State machine transitions: Synchronized access to state change operations
- Event publisher: Thread-safe event publishing with batching capability
Concurrency Control in Matching Algorithm
The matching algorithm will use:
- Read-only operations for candidate driver filtering (no locking required)
- Atomic updates for status changes using database version fields
- Batch processing for concurrent matching requests to reduce contention
- Work queue pattern to serialize critical matching operations when needed
Performance Considerations
- Thread pool sizing: Configurable thread pool with size based on CPU cores and I/O characteristics
- Async processing: Non-blocking operations for external service calls (e.g., geolocation APIs)
- Batching: Concurrent matching requests will be batched when possible to reduce database load
- Resource pooling: Connection pools and thread pools will be tuned for maximum throughput with minimal contention
Testing Strategy for Concurrency
- Stress testing with concurrent matching requests to detect race conditions
- Load testing with multiple simultaneous driver availability updates
- Deadlock detection through timeout-based tests and monitoring
- Idempotency testing with duplicate requests to ensure no side effects
6. Sequence Diagrams for Key Flows
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E-->>A: Event Published
A-->>U: Return Matched Drivers (3)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=started)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=started)
A->>E: Publish TripStartedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=started)
A->>E: Publish TripStartedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=completed)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=started)
A->>E: Publish TripStartedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=completed)
A->>E: Publish TripCompletedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=started)
A->>E: Publish TripStartedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=completed)
A->>E: Publish TripCompletedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Driver Status (driverId, status=available)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=started)
A->>E: Publish TripStartedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=completed)
A->>E: Publish TripCompletedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Driver Status (driverId, status=available)
A->>A: Log Trip Completion (tripId, driverId, passengerId)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
A->>A: Validate Driver Acceptance (driverId, acceptanceTimeout=5min)
A->>A: Handle Timeout (driverId, status=available)
A->>A: Retry Match (passengerId, vehicleType)
A->>L: Get Nearby Drivers (radius=10km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A-->>U: Return Retry Matched Drivers (3)
A->>A: Send Notification to Passenger (passengerId, newDriverId, tripDetails)
A->>A: Update Trip Status (tripId, status=confirmed)
A->>E: Publish TripConfirmedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=started)
A->>E: Publish TripStartedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Trip Status (tripId, status=completed)
A->>E: Publish TripCompletedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Driver Status (driverId, status=available)
A->>A: Log Trip Completion (tripId, driverId, passengerId)
A->>A: Update Driver Stats (driverId, tripsCompleted, rating)
sequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location UpdatedsequenceDiagram
participant U as User (Passenger)
participant A as Driver Matching Service
participant D as Driver Repository
participant L as Location Service
participant E as Event Publisher
participant M as Messaging Queue
U->>A: Request Driver Match (lat, lng, vehicleType)
A->>L: Get Nearby Drivers (radius=5km)
L-->>A: Driver Locations
A->>D: Filter Available Drivers (status=online, vehicleType)
D-->>A: Available Drivers List
A->>A: Calculate Match Score (distance, rating, availability)
A->>A: Select Top 3 Drivers
A->>E: Publish DriverMatchedEvent (driverId, passengerId)
E->>M: Queue Event for Async Processing
M-->>E: Event Queued
A-->>U: Return Matched Drivers (3)
A->>A: Update Driver Status (driverId, status=busy)
A->>A: Send Notification to Driver (driverId, passengerId, tripDetails)
A->>A: Log Match Event (passengerId, driverId, timestamp)
A->>A: Start Trip Timer (driverId, timeout=30min)
A->>A: Set Driver Timeout (driverId, timeout=30min)
A->>A: Update Driver Location (driverId, lat, lng)
A->>L: Update Driver Location in Location Service
L-->>A: Location Updated
Note over A,U: Driver Accepts Trip
U->>A: Accept Trip (tripId, driverId)
A->>D: Update Trip Status (tripId, status=accepted)
D-->>A: Trip Updated
A->>E: Publish TripStartedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Driver Status (driverId, status=onTrip)
A->>A: Log Trip Start (tripId, driverId, passengerId)
A->>A: Start Ride Timer (tripId, timeout=60min)
Note over A,U: Driver Completes Trip
U->>A: Complete Trip (tripId, driverId)
A->>D: Update Trip Status (tripId, status=completed)
D-->>A: Trip Updated
A->>E: Publish TripCompletedEvent (tripId, passengerId, driverId)
E-->>A: Event Published
A->>A: Update Driver Status (driverId, status=available)
A->>A: Log Trip Completion (tripId, driverId, passengerId)
A->>A: Update Driver Stats (driverId, tripsCompleted, rating)
## 7. Caching Design
### Cache Strategy Selection
For the Driver Matching system, we'll use **cache-aside pattern** with **LRU eviction policy**. This approach provides the best balance between performance and consistency for our use case where real-time driver availability and location data are critical.
### Cache Key Design
Cache keys will follow a structured namespace pattern:
driver:{driverId} driver:location:{driverId} driver:availability:{driverId} booking:{bookingId} booking:driver:{bookingId}
### TTL Strategy
| Data Type | TTL | Reason |
|-----------|-----|--------|
| Driver Profile | 24h | Static data changes infrequently |
| Driver Location | 5m | Real-time location updates required |
| Driver Availability | 10m | Frequent availability changes |
| Booking Details | 30m | Active bookings have short lifecycle |
| Driver Match Results | 2m | Fresh matching results needed |
### Cache Eviction Policy
LRU (Least Recently Used) with probabilistic early expiry to prevent cache stampede:
- Primary eviction: LRU
- Secondary: Random TTL adjustment (±15%) to spread load
- Cache warming on startup for frequently accessed driver profiles
### Cache Stampede Prevention
Implement distributed lock pattern with Redis:
```java
public class CacheStampedePrevention {
private static final String LOCK_PREFIX = "cache_lock:";
private static final int LOCK_TTL_MS = 5000;
public String getWithLock(String key, Supplier<String> cacheMissHandler) {
String lockKey = LOCK_PREFIX + key;
String lockValue = UUID.randomUUID().toString();
try {
if (redisTemplate.setIfAbsent(lockKey, lockValue,
Duration.ofMillis(LOCK_TTL_MS))) {
// Acquired lock, proceed with cache miss handling
String result = cacheMissHandler.get();
redisTemplate.expire(key, Duration.ofMinutes(5));
return result;
} else {
// Wait briefly and retry
Thread.sleep(100);
return redisTemplate.get(key);
}
} finally {
// Release lock if owned
releaseLock(lockKey, lockValue);
}
}
private void releaseLock(String lockKey, String lockValue) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), lockValue);
}
}
Distributed Cache Architecture
Using Redis Cluster with:
- 3 master nodes for high availability
- 3 replica nodes per master for read scaling
- Redis Sentinel monitoring for automatic failover
- Cross-region replication for disaster recovery
Cache Invalidation Strategy
Event-driven invalidation using the outbox pattern:
- When driver location updates, publish
DriverLocationUpdatedevent - When booking status changes, publish
BookingStatusChangedevent - Cache consumers listen to these events and invalidate relevant keys
- For immediate consistency, use cache invalidation on write operations
@Component
public class CacheInvalidationService {
private final RedisTemplate<String, Object> redisTemplate;
@EventListener
public void handleDriverLocationUpdate(DriverLocationUpdatedEvent event) {
String key = "driver:location:" + event.getDriverId();
redisTemplate.delete(key);
// Also invalidate matching cache if needed
String matchKey = "driver:match:" + event.getBookingId();
redisTemplate.delete(matchKey);
}
@EventListener
public void handleBookingStatusChange(BookingStatusChangedEvent event) {
String key = "booking:" + event.getBookingId();
redisTemplate.delete(key);
}
}
8. Repository / Data Access Layer
Repository Pattern Implementation
The repository pattern is implemented to abstract data access logic and provide a clean separation between domain logic and database operations. Each entity will have its own repository interface with common CRUD operations and specialized queries.
public interface DriverRepository {
Optional<Driver> findById(UUID id);
List<Driver> findByLocation(Point location, double radiusKm);
List<Driver> findAvailableDrivers(Point location, double radiusKm, int limit);
void save(Driver driver);
void update(Driver driver);
void delete(UUID id);
List<Driver> findAll();
}
public interface TripRepository {
Optional<Trip> findById(UUID id);
List<Trip> findByDriverId(UUID driverId);
List<Trip> findByStatus(TripStatus status);
void save(Trip trip);
void update(Trip trip);
}
ORM vs Raw Query Decisions
For the Driver Matching system, we use JPA/Hibernate as our ORM for most operations due to:
- Type safety and compile-time checking
- Reduced boilerplate code
- Easy mapping between entities and database tables
- Support for lazy loading and caching
However, for performance-critical queries like driver location searches, we use raw SQL to leverage database-specific optimizations:
@Repository
public class DriverRepositoryImpl implements DriverRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Driver> findByLocation(Point location, double radiusKm) {
String hql = """
SELECT d FROM Driver d
WHERE ST_Distance_Sphere(
POINT(d.location.longitude, d.location.latitude),
POINT(:longitude, :latitude)
) <= :radius
""";
return entityManager.createQuery(hql, Driver.class)
.setParameter("longitude", location.getLongitude())
.setParameter("latitude", location.getLatitude())
.setParameter("radius", radiusKm * 1000)
.getResultList();
}
// Raw SQL for better performance on spatial queries
public List<Driver> findAvailableDriversRaw(Point location, double radiusKm, int limit) {
String sql = """
SELECT d.* FROM drivers d
WHERE ST_Distance_Sphere(
POINT(d.longitude, d.latitude),
POINT(:longitude, :latitude)
) <= :radius
AND d.status = 'AVAILABLE'
ORDER BY ST_Distance_Sphere(
POINT(d.longitude, d.latitude),
POINT(:longitude, :latitude)
)
LIMIT :limit
""";
Query query = entityManager.createNativeQuery(sql, Driver.class);
query.setParameter("longitude", location.getLongitude());
query.setParameter("latitude", location.getLatitude());
query.setParameter("radius", radiusKm * 1000);
query.setParameter("limit", limit);
return query.getResultList();
}
}
Read vs Write Separation
We implement CQRS pattern for read-heavy operations to optimize performance:
// Read model - optimized for queries
public class DriverReadModel {
private UUID id;
private String name;
private Point location;
private double rating;
private boolean isAvailable;
// Getters and setters
}
// Write model - optimized for mutations
public class Driver {
private UUID id;
private String name;
private Point location;
private double rating;
private DriverStatus status;
private LocalDateTime lastUpdated;
// Getters and setters
}
// Read repository for optimized queries
@Repository
public class DriverReadRepository {
@PersistenceContext
private EntityManager entityManager;
public List<DriverReadModel> findAvailableDriversInRadius(Point center, double radiusKm) {
String sql = """
SELECT d.id, d.name, d.longitude, d.latitude, d.rating, d.is_available
FROM drivers_read d
WHERE ST_Distance_Sphere(
POINT(d.longitude, d.latitude),
POINT(:longitude, :latitude)
) <= :radius
AND d.is_available = TRUE
ORDER BY ST_Distance_Sphere(
POINT(d.longitude, d.latitude),
POINT(:longitude, :latitude)
)
""";
Query query = entityManager.createNativeQuery(sql);
query.setParameter("longitude", center.getLongitude());
query.setParameter("latitude", center.getLatitude());
query.setParameter("radius", radiusKm * 1000);
return query.getResultList().stream()
.map(this::mapToReadModel)
.collect(Collectors.toList());
}
}
Connection Pool Configuration
Connection pool is configured with:
- Max pool size: 50
- Min idle connections: 10
- Connection timeout: 30 seconds
- Validation query: SELECT 1
# application.yml
spring:
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10
connection-timeout: 30000
validation-timeout: 5000
idle-timeout: 600000
max-lifetime: 1800000
N+1 Query Prevention Strategy
We use eager loading and batch fetching to prevent N+1 queries:
@Entity
@Table(name = "trips")
@NamedEntityGraph(
name = "trip-with-driver",
attributeNodes = @NamedAttributeNode(value = "driver", subgraph = "driver-subgraph"),
subgraphs = @NamedSubgraph(
name = "driver-subgraph",
attributeNodes = @NamedAttributeNode("location")
)
)
public class Trip {
@Id
private UUID id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "driver_id")
private Driver driver;
// ... other fields
@OneToMany(mappedBy = "trip", fetch = FetchType.LAZY)
private List<TripEvent> events;
}
// In service layer, we explicitly fetch with joins:
public List<Trip> getRecentTripsWithDriverInfo() {
String hql = """
SELECT t FROM Trip t
JOIN FETCH t.driver d
WHERE t.createdAt >= :date
ORDER BY t.createdAt DESC
""";
return entityManager.createQuery(hql, Trip.class)
.setParameter("date", LocalDateTime.now().minusDays(7))
.getResultList();
}
Batch Operation Design
For bulk operations like driver status updates:
@Repository
public class DriverBatchRepository {
@PersistenceContext
private EntityManager entityManager;
public void updateDriverStatusBatch(List<UUID> driverIds, DriverStatus newStatus) {
String hql = """
UPDATE Driver d
SET d.status = :status, d.lastUpdated = :lastUpdated
WHERE d.id IN (:ids)
""";
Query query = entityManager.createQuery(hql);
query.setParameter("status", newStatus);
query.setParameter("lastUpdated", LocalDateTime.now());
query.setParameter("ids", driverIds);
int updatedCount = query.executeUpdate();
// Log batch operation statistics
}
public void saveDriversBatch(List<Driver> drivers) {
int batchSize = 50;
for (int i = 0; i < drivers.size(); i += batchSize) {
List<Driver> batch = drivers.subList(i, Math.min(i + batchSize, drivers.size()));
// Use bulk insert with batch size control
for (Driver driver : batch) {
entityManager.persist(driver);
}
entityManager.flush();
entityManager.clear();
}
}
}
9. Asynchronous Processing & Messaging
Event-driven boundaries in this feature
The driver matching system will use a hybrid synchronous/asynchronous approach. Core matching operations (driver selection, trip assignment) will be handled synchronously to ensure immediate response for user requests. However, several secondary operations will be decoupled via asynchronous messaging to improve system responsiveness and reliability:
- Driver status updates: When a driver's location or availability changes, this should be processed asynchronously to avoid blocking the primary matching flow.
- Trip completion notifications: When a trip is completed, this should trigger asynchronous processing for billing, rating collection, and driver performance tracking.
- Real-time matching notifications: When a driver is matched to a trip, the system should asynchronously notify relevant parties (user app, driver app) without blocking the matching process.
- Driver availability updates: Periodic or event-driven updates to driver availability status should be handled asynchronously.
Message schema design with sample payload
{
"version": "1.0",
"eventId": "uuid-456789",
"eventType": "driver.status.changed",
"timestamp": "2023-11-15T14:30:00Z",
"payload": {
"driverId": "driver-12345",
"status": "available",
"location": {
"latitude": 37.7749,
"longitude": -122.4194
},
"timestamp": "2023-11-15T14:30:00Z"
}
}
{
"version": "1.0",
"eventId": "uuid-789012",
"eventType": "trip.assigned",
"timestamp": "2023-11-15T14:30:00Z",
"payload": {
"tripId": "trip-56789",
"driverId": "driver-12345",
"userId": "user-98765",
"assignedAt": "2023-11-15T14:30:00Z"
}
}
Dead letter queue strategy and poison message handling
The system will implement a dead-letter queue (DLQ) for failed message processing. Messages that fail to process after 3 retries will be moved to a DLQ with a retention period of 7 days. The system will:
- Log the failed message and its context to a monitoring system
- Send an alert to on-call engineers for manual investigation
- Store the failed message in a separate database table with metadata for debugging
- Implement retry logic with exponential backoff (1s, 5s, 30s) before moving to DLQ
At-least-once vs. exactly-once delivery
The system will use at-least-once delivery for most operations. This is chosen because:
- Idempotency: Most operations are designed to be idempotent (e.g., status updates, trip assignments)
- Simplicity: At-least-once is simpler to implement and debug than exactly-once
- Performance: Avoids the complexity of maintaining transaction state across distributed systems
For critical operations like billing, we will implement idempotency keys to ensure duplicate messages don't cause double billing.
Retry policy for consumers
public class MessageRetryPolicy {
private static final int MAX_RETRIES = 3;
private static final long INITIAL_DELAY_MS = 1000;
private static final long MAX_DELAY_MS = 30000;
public boolean shouldRetry(int attempt, Exception exception) {
if (attempt >= MAX_RETRIES) return false;
// Don't retry on certain fatal errors
if (exception instanceof ValidationException ||
exception instanceof IllegalArgumentException) {
return false;
}
return true;
}
public long getDelay(int attempt) {
long delay = INITIAL_DELAY_MS * (1L << attempt);
return Math.min(delay, MAX_DELAY_MS);
}
}
Outbox pattern for reliable event publishing
The outbox pattern will be implemented to ensure message delivery reliability:
@Component
public class EventPublisher {
@Autowired
private OutboxRepository outboxRepository;
@Autowired
private MessageProducer messageProducer;
public void publishEvent(DriverStatusChangedEvent event) {
// 1. Save to outbox table (within same transaction)
OutboxMessage outboxMessage = new OutboxMessage(
UUID.randomUUID().toString(),
"driver.status.changed",
event,
System.currentTimeMillis()
);
outboxRepository.save(outboxMessage);
// 2. Publish to message broker (if successful, mark as published)
try {
messageProducer.publish(outboxMessage);
outboxRepository.markAsPublished(outboxMessage.getId());
} catch (Exception e) {
// Retry logic or DLQ handling
throw new EventPublishingException("Failed to publish event", e);
}
}
}
Sequence diagram for async flows
sequenceDiagram
participant User
participant APIGateway
participant DriverMatchingService
participant MessageBroker
participant DriverStatusConsumer
participant TripAssignmentConsumer
User->>APIGateway: Request trip
APIGateway->>DriverMatchingService: MatchDriver()
DriverMatchingService->>DriverMatchingService: FindAvailableDrivers()
DriverMatchingService->>MessageBroker: Publish: driver.status.changed
MessageBroker-->>DriverStatusConsumer: Event delivered
DriverMatchingService->>DriverMatchingService: AssignTrip()
DriverMatchingService->>MessageBroker: Publish: trip.assigned
MessageBroker-->>TripAssignmentConsumer: Event delivered
Note over DriverStatusConsumer: Async processing of driver status update
Note over TripAssignmentConsumer: Async processing of trip assignment
DriverStatusConsumer->>DriverStatusConsumer: Update driver availability
TripAssignmentConsumer->>TripAssignmentConsumer: Notify user and driver
DriverMatchingService-->>APIGateway: Return trip details
APIGateway-->>User: Response with assigned driver
10. Error Handling Design
Error Classification Hierarchy
The system follows a three-level error classification hierarchy:
- Domain Errors - Business logic violations (e.g., driver not available, location invalid)
- Infrastructure Errors - System-level issues (e.g., database connection failure, cache timeout)
- Validation Errors - Input format and constraints violations (e.g., missing required fields, invalid format)
public enum ErrorType {
VALIDATION_ERROR,
DOMAIN_ERROR,
INFRASTRUCTURE_ERROR
}
public class ErrorDetail {
private String code;
private String message;
private ErrorType type;
private Map<String, Object> context;
}
Error Propagation Strategy
Errors are propagated up the call stack with appropriate wrapping to preserve context. Service layer errors are wrapped in domain-specific exceptions, while infrastructure issues are wrapped in system-level exceptions.
public class DriverMatchingException extends RuntimeException {
private final ErrorDetail errorDetail;
public DriverMatchingException(ErrorDetail errorDetail, Throwable cause) {
super(errorDetail.getMessage(), cause);
this.errorDetail = errorDetail;
}
public ErrorDetail getErrorDetail() {
return errorDetail;
}
}
public class DriverNotFoundException extends DriverMatchingException {
public DriverNotFoundException(String driverId) {
super(new ErrorDetail("DRIVER_NOT_FOUND", "Driver with ID " + driverId + " not found"), null);
}
}
Retry Strategy with Exponential Backoff and Jitter
Retry logic is implemented using exponential backoff with jitter to prevent thundering herd:
public class RetryableOperation {
private static final int MAX_RETRIES = 3;
private static final long BASE_DELAY_MS = 1000;
private static final double JITTER_FACTOR = 0.2;
public <T> T executeWithRetry(Supplier<T> operation) {
int attempts = 0;
while (attempts < MAX_RETRIES) {
try {
return operation.get();
} catch (InfrastructureException e) {
attempts++;
if (attempts >= MAX_RETRIES) {
throw new RetryExhaustedException("Operation failed after " + MAX_RETRIES + " attempts", e);
}
long delay = (long) (BASE_DELAY_MS * Math.pow(2, attempts) *
(1 + JITTER_FACTOR * (Math.random() - 0.5)));
try {
Thread.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RetryExhaustedException("Retry interrupted", ie);
}
}
}
throw new RetryExhaustedException("Operation failed after "MAX_RETRIES + " attempts");
}
}
Circuit Breaker Design
Circuit breaker pattern prevents cascading failures in downstream services:
public class CircuitBreaker {
private final int failureThreshold;
private final long timeoutMs;
private final int successThreshold;
private volatile CircuitState state = CircuitState.CLOSED;
private volatile int failureCount = 0;
private volatile long lastFailureTime = 0;
public enum CircuitState {
CLOSED, OPEN, HALF_OPEN
}
public <T> T execute(Supplier<T> operation) {
switch (state) {
case CLOSED:
return executeWithCircuitClosed(operation);
case OPEN:
if (System.currentTimeMillis() - lastFailureTime > timeoutMs) {
state = CircuitState.HALF_OPEN;
return executeWithCircuitHalfOpen(operation);
}
throw new CircuitBreakerOpenException("Circuit is open");
case HALF_OPEN:
return executeWithCircuitHalfOpen(operation);
}
throw new IllegalStateException("Invalid circuit state");
}
private <T> T executeWithCircuitClosed(Supplier<T> operation) {
try {
T result = operation.get();
failureCount = 0;
return result;
} catch (Exception e) {
failureCount++;
lastFailureTime = System.currentTimeMillis();
if (failureCount >= failureThreshold) {
state = CircuitState.OPEN;
}
throw e;
}
}
private <T> T executeWithCircuitHalfOpen(Supplier<T> operation) {
try {
T result = operation.get();
failureCount = 0;
state = CircuitState.CLOSED;
return result;
} catch (Exception e) {
state = CircuitState.OPEN;
lastFailureTime = System.currentTimeMillis();
throw e;
}
}
}
Fallback Behavior Design
Fallbacks are implemented for critical external dependencies:
public class DriverMatchingService {
private final ExternalDriverService externalDriverService;
public List<Driver> findMatchingDrivers(Location pickupLocation, int radius) {
try {
return externalDriverService.findNearbyDrivers(pickupLocation, radius);
} catch (InfrastructureException e) {
// Fallback to cached driver list or default behavior
logger.warn("External service unavailable, using fallback", e);
return getCachedDrivers(pickupLocation, radius);
}
}
}
Error Logging Format and Alerting Thresholds
Structured logging includes trace context and error details:
public class ErrorLogger {
private static final Logger logger = LoggerFactory.getLogger(ErrorLogger.class);
public void logError(String operation, ErrorDetail errorDetail,
Map<String, Object> context) {
Map<String, Object> logContext = new HashMap<>();
logContext.put("operation", operation);
logContext.put("error_code", errorDetail.getCode());
logContext.put("error_message", errorDetail.getMessage());
logContext.put("error_type", errorDetail.getType().name());
logContext.put("context", context);
// Add trace and span IDs if available
String traceId = MDC.get("trace_id");
if (traceId != null) {
logContext.put("trace_id", traceId);
}
logger.error("Driver matching error occurred: {}", logContext);
}
}
Alerting thresholds:
| Error Type | Alert Threshold | Severity |
|---|---|---|
| Infrastructure Errors | > 10 errors/minute | Critical |
| Domain Errors | > 50 errors/minute | Warning |
| Validation Errors | > 100 errors/minute | Info |
Error Handling in API Layer
API layer catches domain exceptions and converts them to appropriate HTTP responses:
@RestController
@RequestMapping("/api/v1/drivers")
public class DriverController {
@GetMapping("/matching")
public ResponseEntity<List<Driver>> findMatchingDrivers(
@RequestParam double lat,
@RequestParam double lng,
@RequestParam int radius) {
try {
List<Driver> drivers = driverMatchingService.findMatchingDrivers(
new Location(lat, lng), radius);
return ResponseEntity.ok(drivers);
} catch (DriverNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (ValidationException e) {
return ResponseEntity.badRequest().body(Collections.emptyList());
} catch (InfrastructureException e) {
logger.error("Infrastructure error in driver matching", e);
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).build();
}
}
}
Error Response Format
Standardized error response format:
{
"error": {
"code": "DRIVER_NOT_FOUND",
"message": "No drivers available in the requested location",
"type": "DOMAIN_ERROR",
"timestamp": "2023-10-25T14:30:00Z",
"context": {
"location": {
"lat": 40.7128,
"lng": -74.0060
},
"radius": 5000
}
}
}
11. Security Design
Input Validation and Sanitization Strategy
All API inputs are validated using a combination of:
- Bean Validation (JSR-303) for structured validation (e.g.,
@NotNull,@Min,@Pattern) - Custom validators for business logic constraints (e.g., valid driver license format, GPS coordinate ranges)
- OWASP Java HTML Sanitizer for any text fields that might contain user-generated content to prevent XSS attacks
public class DriverRegistrationRequest {
@NotBlank
@Pattern(regexp = "^[A-Z]{2}[0-9]{10}$", message = "Invalid license format")
private String licenseNumber;
@Min(0)
@Max(180)
private Double latitude;
@Min(-180)
@Max(180)
private Double longitude;
}
SQL Injection Prevention
- Prepared statements used exclusively for all database queries
- No dynamic query building from user inputs
- Parameterized queries with strict type checking
Authentication Middleware Design
- JWT-based authentication for driver sessions
- OAuth2 integration with Google/Facebook for initial login
- Token refresh mechanism with 15-minute access tokens and 7-day refresh tokens
- Session invalidation on logout or token compromise detection
Authorization Enforcement
- Role-Based Access Control (RBAC) with driver roles:
DRIVER: Can access own profile, location, and ride requestsADMIN: Can manage drivers, view analytics, and override matches
- Fine-grained permissions checked at service layer using
@PreAuthorizeannotations in Spring Security
@PreAuthorize("hasRole('DRIVER') and #driverId == authentication.principal.driverId")
public void updateDriverProfile(@PathVariable String driverId, @RequestBody DriverProfileUpdateRequest request) {
// Implementation
}
Sensitive Data Encryption
- At rest: AES-256 encryption using AWS KMS for driver PII (license, phone, address)
- In transit: TLS 1.3 with HSTS enabled for all API endpoints
- PII masking in logs and observability tools using redaction patterns
PII Handling and Masking Strategy
- Driver license numbers: Partially masked in logs (e.g.,
XX-XXXX-XXXX) - Phone numbers: Masked to country code + last 4 digits (e.g.,
+1 XXX-XXX-1234) - Addresses: Only latitude/longitude coordinates stored; full address never persisted
Secrets Management
- AWS Secrets Manager for storing:
- Database credentials (rotated every 90 days)
- OAuth2 client secrets
- JWT signing keys (rotated every 30 days)
- Secure key rotation with automatic failover and A/B key validation
Audit Logging Design
- Events logged:
- Driver login/logout
- Ride match decisions
- Profile updates
- Location updates
- Log format:
{
"timestamp": "2023-10-15T14:30:45.123Z",
"trace_id": "abc123def456",
"user_id": "driver_12345",
"event_type": "DRIVER_LOCATION_UPDATE",
"data": {
"latitude": 40.7128,
"longitude": -74.0060
},
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0..."
}
- Retention policy: 90 days for audit logs, 30 days for operational logs
- PII masking in all logged fields using regex patterns
12. Testing Strategy
Unit Testing Scope and Coverage Targets
Unit tests will cover 85%+ of business logic and service layer code. Focus areas include:
- Driver matching algorithm implementation
- Location-based distance calculations
- Driver availability state transitions
- Request routing and queuing logic
- Business rule validations (capacity, eligibility, etc.)
- Error handling paths in service methods
Integration Testing Strategy
Database Integration Tests:
- Use testcontainers with PostgreSQL for end-to-end database interactions
- Test transaction boundaries and data consistency across operations
- Validate read/write separation patterns (CQRS) with separate read/write databases
External Service Integration Tests:
- Mock geocoding and distance calculation services using WireMock or Mockito
- Test API gateway integration with external payment processors and SMS services
- Validate event publishing to message broker (e.g., Kafka) with proper error handling
Service Layer Integration Tests:
- Test complete request flow from API to database and external services
- Validate saga orchestration patterns for distributed transactions
- Test cache interaction with Redis mock or embedded instance
Contract Testing
Implement contract testing using Pact for consumer-driven contracts between:
- Driver Matching Service and Payment Service (for fare estimation)
- Driver Matching Service and Notification Service (for ride updates)
- Driver Matching Service and Location Service (for real-time GPS tracking)
Performance Testing Tool
Use k6 for load testing with key scenarios:
- Simultaneous ride requests from multiple users (1000 concurrent)
- Peak hour matching under high driver availability
- Latency-sensitive matching operations (under 200ms p95)
- Throughput testing for 10,000 requests per minute
Test Data Management Strategy
Fixtures and Factories:
- Create driver and user fixtures with realistic location coordinates (within 50km radius)
- Use factory pattern to generate test data for:
- Driver availability states (available, busy, offline)
- Ride request scenarios (short/long distance, peak/non-peak hours)
- Geographic zones with different matching priorities
Data Seeding:
- Pre-populate test database with 10,000+ driver records across different zones
- Seed with historical ride data for performance benchmarking
- Use database snapshots for consistent testing environments
Test Data Isolation:
- Use separate test databases per environment (dev/staging)
- Implement data cleanup strategy to reset test state between runs
- Use UUIDs for test entities to prevent cross-test contamination
// Example unit test structure for driver matching logic
@Test
public void testDriverMatchingAlgorithm() {
// Given
List<Driver> drivers = driverFactory.createAvailableDrivers(10);
RideRequest request = rideRequestFactory.createRequest(
new Location(40.7128, -74.0060),
new Location(40.7589, -73.9851)
);
// When
List<Driver> matchedDrivers = driverMatchingService.findMatchingDrivers(request);
// Then
assertThat(matchedDrivers).hasSize(3);
assertThat(matchedDrivers).allSatisfy(driver ->
assertThat(driver.getAvailability()).isEqualTo(DriverAvailability.AVAILABLE)
);
}
13. Observability Design
Structured Logging Schema
All logs must follow a consistent schema with the following required fields:
timestamp: ISO8601 formatted UTC timestamptrace_id: OpenTelemetry trace identifier (32-character hex string)span_id: OpenTelemetry span identifier (16-character hex string)service_name: "driver-matching-service"level: ERROR | WARN | INFO | DEBUGmessage: Human-readable log messageuser_id: Optional, masked for PII (first 4 chars + ***)driver_id: Optional, masked for PII (first 4 chars + ***)request_id: Unique identifier for the incoming requesterror_code: Application-specific error code (e.g., "DRIVER_NOT_FOUND")error_stack: Optional, full stack trace for ERROR level logsduration_ms: Operation execution time in milliseconds
PII masking strategy:
public class PiiMasker {
public static String maskUserId(String userId) {
if (userId == null || userId.length() <= 4) return userId;
return userId.substring(0, 4) + "***";
}
}
Distributed Tracing Instrumentation
OpenTelemetry is used for distributed tracing with custom spans:
Key Operations to Trace:
driver_matching_service.match_driver- Main matching logicdriver_matching_service.fetch_driver_location- Location fetch from DBdriver_matching_service.publish_driver_status_event- Event publishingdriver_matching_service.validate_request- Request validation
Span Attributes:
request_id: Unique request identifierdriver_count: Number of drivers considered in matchingmatch_distance_km: Distance threshold for matchinguser_location.lat: User's latitude coordinateuser_location.lng: User's longitude coordinate
Metrics Design
RED Method Metrics:
| Metric Name | Type | Labels | Description |
|---|---|---|---|
driver_matching_requests_total | Counter | status, error_code | Total request count |
driver_matching_errors_total | Counter | error_type, operation | Error count by type |
driver_matching_duration_seconds | Histogram | operation, status | Request processing time |
driver_matching_active_requests | Gauge | operation | Currently active requests |
driver_matching_cache_hit_ratio | Gauge | cache_type | Cache hit rates |
driver_matching_queue_size | Gauge | queue_name | Async queue sizes |
Sample Histogram Configuration:
Histogram durationHistogram = Histogram.builder()
.name("driver_matching_duration_seconds")
.description("Driver matching operation duration")
.unit("seconds")
.buckets(Buckets.linear(0.01, 0.05, 20)) // 0.01s to 1s buckets
.build();
Health Check Endpoint Design
Endpoint: /health
{
"status": "healthy",
"timestamp": "2023-12-01T10:00:00Z",
"service": "driver-matching-service",
"dependencies": {
"database": "healthy",
"redis": "healthy",
"event-broker": "healthy"
}
}
Endpoint: /ready
{
"status": "ready",
"timestamp": "2023-12-01T10:00:00Z",
"service": "driver-matching-service",
"readiness_checks": {
"database_connection": true,
"cache_connected": true,
"message_broker_ready": true
}
}
Endpoint: /live
{
"status": "alive",
"timestamp": "2023-12-01T10:00:00Z",
"service": "driver-matching-service",
"uptime_seconds": 3600
}
Alerting Thresholds
Critical Alerts (require on-call escalation):
driver_matching_errors_total> 100 errors per minute (5 min window)driver_matching_duration_secondsp99 > 500ms (1 min window)driver_matching_requests_totalrate drops below 10 req/sec for 5 min
Warning Alerts:
driver_matching_duration_secondsp99 > 200ms (1 min window)driver_matching_cache_hit_ratio< 70% (5 min window)- Database connection pool utilization > 85%
On-call escalation path:
- On-call engineer notified via Slack
- If no response within 5 min, escalate to primary team
- If issue persists for 15 min, trigger incident response process
Service-Level Metrics Collection
Metrics Endpoint: /metrics (Prometheus format)
# HELP driver_matching_requests_total Total driver matching requests
# TYPE driver_matching_requests_total counter
driver_matching_requests_total{status="success"} 12345
driver_matching_requests_total{status="error",error_code="NO_DRIVERS_FOUND"} 123
# HELP driver_matching_duration_seconds Driver matching operation duration
# TYPE driver_matching_duration_seconds histogram
driver_matching_duration_seconds_bucket{operation="match_driver",le="0.1"} 500
driver_matching_duration_seconds_bucket{operation="match_driver",le="0.5"} 1200
driver_matching_duration_seconds_bucket{operation="match_driver",le="+Inf"} 1500
driver_matching_duration_seconds_sum{operation="match_driver"} 345.67
driver_matching_duration_seconds_count{operation="match_driver"} 1500
14. Open Questions, Risks & Trade-offs
Decisions requiring product / engineering alignment
- Matching algorithm complexity vs. performance trade-off: Should we implement a simple distance-based matching or invest in machine learning models for dynamic driver preferences and traffic patterns? The latter requires extensive training data and real-time inference capabilities but provides better user experience.
- Driver onboarding process: Should drivers be required to complete full verification (document upload, background checks) before being matched, or should we enable "soft matching" with basic verification and complete verification asynchronously?
- Pricing model integration: How should we handle dynamic pricing (surge pricing) in the matching algorithm? Should drivers receive price adjustments to influence their availability, or should we treat pricing as a separate optimization layer?
Known trade-offs and justifications
- Real-time matching vs. batch processing: Real-time matching provides better user experience but increases system complexity and latency. Batch processing reduces operational overhead but introduces delay in driver availability updates.
- Consistency vs. availability: We must choose between strong consistency for matching accuracy and high availability for system responsiveness. For example, using eventual consistency for driver location updates allows better availability but may result in outdated matching information.
- Geofence granularity: Fine-grained geofences provide better matching accuracy but increase memory and processing overhead. Coarse-grained geofences reduce resource usage but may result in suboptimal matches.
Risks and mitigation strategies
- Driver availability imbalance: Risk of having too many drivers in high-demand areas and too few in low-demand areas. Mitigation includes implementing dynamic driver incentives, traffic-based pricing, and predictive demand forecasting.
- System overload during peak hours: Risk of system failure or degraded performance during high-traffic periods. Mitigation includes implementing rate limiting, horizontal scaling strategies, and pre-warming of system capacity.
- Driver fraud and abuse: Risk of drivers providing false location data or manipulating matching algorithms. Mitigation includes implementing location validation checks, behavioral pattern analysis, and fraud detection systems.
- Data privacy compliance: Risk of non-compliance with data protection regulations (GDPR, CCPA) when collecting and processing driver location and personal information. Mitigation includes implementing proper data encryption, access controls, and compliance monitoring systems.
Technical debt acknowledged
- Algorithmic bias: The matching algorithm may inadvertently favor certain driver demographics or geographic regions due to historical data patterns. This technical debt requires ongoing monitoring and algorithmic bias correction mechanisms.
- Legacy system integration: Integration with existing payment, verification, and dispatch systems may introduce technical debt through tight coupling and outdated communication protocols.
- Cache invalidation complexity: The caching strategy introduces complexity in maintaining data consistency across multiple systems and services, particularly during system upgrades or maintenance windows.
- Eventual consistency challenges: The distributed nature of the matching system introduces eventual consistency issues that may lead to temporary mismatches or stale data states requiring additional error handling and recovery mechanisms.