Skip to main content

Driver Matching System Low-Level Design

Comprehensive LLD for a real-time driver matching system that efficiently pairs drivers with passengers based on location, availability, and preferences.

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

  1. 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?
  2. 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?
  3. 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?
  4. What is the read/write ratio for matching operations?

    • Is it predominantly driver registration and location updates, or mostly matching requests?
  5. What are the latency sensitivity requirements?

    • What is the maximum acceptable time for a match to be returned (e.g., <50ms, <200ms)?
  6. 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?
  7. 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

MethodPathDescriptionAuthRate Limit
POST/v1/driver/requestsSubmit driver location and availabilityJWT100 req/min
GET/v1/driver/requests/{id}Get driver request statusJWT50 req/min
POST/v1/driver/requests/{id}/acceptAccept driver requestJWT50 req/min
POST/v1/driver/requests/{id}/rejectReject driver requestJWT50 req/min
POST/v1/driver/requests/{id}/updateUpdate driver locationJWT100 req/min
GET/v1/driver/requests/{id}/statusGet request statusJWT50 req/min
POST/v1/driver/requests/{id}/completeComplete driver requestJWT50 req/min
GET/v1/drivers/nearbyFind nearby driversJWT50 req/min
GET/v1/drivers/{id}/profileGet driver profileJWT50 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-Key header
  • For REST, clients can use the X-Idempotency-Key header

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:

  1. Distributed system compatibility: UUIDs ensure uniqueness across distributed systems without coordination
  2. No sequential patterns: Prevents predictability and potential security concerns
  3. Scalability: No need for centralized ID generation, avoiding bottlenecks
  4. Cross-system integration: Easier to merge data from different systems

Index Design

Index NameFieldsTypePurpose
idx_driver_location_timedriver_id, recorded_atCompositeEfficient location history queries
idx_location_coordinateslatitude, longitudeSpatialFast geospatial queries for matching
idx_driver_availability_timedriver_id, start_time, end_timeCompositeFast availability window queries
idx_driver_availability_statusdriver_id, statusCompositeEfficient availability status lookups
idx_driver_matches_triptrip_idSingle fieldFast lookup by trip ID
idx_drivers_user_iduser_idSingle fieldFast lookup by user ID
idx_drivers_statusstatusSingle fieldFast filtering by driver status

Data Partitioning / Sharding Strategy

Geographic sharding based on driver location:

  1. 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
  2. Shard key: latitude and longitude ranges with a hash-based sharding approach

  3. Cross-shard queries: Handled through coordinator service that routes to appropriate shards

Soft Delete vs. Hard Delete Decision

Soft delete is chosen because:

  1. Audit trail requirement: Need to maintain historical match data for analytics and dispute resolution
  2. Compliance: Regulatory requirements may mandate data retention
  3. Data integrity: Prevents orphaned references in related tables
  4. Recovery capability: Ability to restore deleted drivers or matches if needed

Audit Trail Design

All tables include created_at and updated_at timestamps. Additionally:

  1. Change tracking table (not shown) that logs:
    • operation_type (INSERT/UPDATE/DELETE)
    • changed_fields (JSON array of modified fields)
    • old_values and new_values (JSONB for complex objects)
    • user_id (if available)
    • timestamp

Data Archival Strategy and Retention Policy

  1. Driver match history:

    • Keep 2 years of active match data
    • Archive to cold storage (S3 Glacier) after 2 years
  2. Location history:

    • Keep 30 days of location data for matching
    • Archive to cold storage after 30 days
  3. Driver status changes:

    • Keep 5 years of status change history
    • Archive to data warehouse for analytics
  4. 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:

  1. DriverMatchingService - Main orchestration service for matching drivers to ride requests
  2. RideRequestRepository - Data access layer for ride request entities
  3. DriverRepository - Data access layer for driver entities
  4. LocationService - Geospatial calculation utilities
  5. 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:

  1. Database-level atomic operations for critical updates (e.g., driver availability status, matching state transitions)
  2. Optimistic locking using version fields in entities to detect concurrent modifications
  3. Database transactions for atomic operations across multiple related entities
  4. 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:

  1. Fixed ordering of database operations - all operations on related entities follow a consistent order (e.g., always acquire locks in the same sequence)
  2. Timeout-based lock acquisition - maximum wait time of 500ms for any database locks
  3. Transaction isolation level set to READ COMMITTED to prevent phantom reads
  4. Circuit breaker pattern for database operations to prevent cascading failures

Idempotency Implementation for Concurrent Duplicate Requests

Idempotency will be implemented using:

  1. Idempotency keys in request headers for matching operations (e.g., X-Idempotency-Key)
  2. Database-level uniqueness constraints on matching request identifiers
  3. 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

  1. Driver availability cache: Uses ConcurrentHashMap with atomic operations
  2. Matching queue: Thread-safe queue implementation with bounded capacity
  3. State machine transitions: Synchronized access to state change operations
  4. Event publisher: Thread-safe event publishing with batching capability

Concurrency Control in Matching Algorithm

The matching algorithm will use:

  1. Read-only operations for candidate driver filtering (no locking required)
  2. Atomic updates for status changes using database version fields
  3. Batch processing for concurrent matching requests to reduce contention
  4. Work queue pattern to serialize critical matching operations when needed

Performance Considerations

  1. Thread pool sizing: Configurable thread pool with size based on CPU cores and I/O characteristics
  2. Async processing: Non-blocking operations for external service calls (e.g., geolocation APIs)
  3. Batching: Concurrent matching requests will be batched when possible to reduce database load
  4. Resource pooling: Connection pools and thread pools will be tuned for maximum throughput with minimal contention

Testing Strategy for Concurrency

  1. Stress testing with concurrent matching requests to detect race conditions
  2. Load testing with multiple simultaneous driver availability updates
  3. Deadlock detection through timeout-based tests and monitoring
  4. 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:

  1. When driver location updates, publish DriverLocationUpdated event
  2. When booking status changes, publish BookingStatusChanged event
  3. Cache consumers listen to these events and invalidate relevant keys
  4. 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:

  1. Log the failed message and its context to a monitoring system
  2. Send an alert to on-call engineers for manual investigation
  3. Store the failed message in a separate database table with metadata for debugging
  4. 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:

  1. Idempotency: Most operations are designed to be idempotent (e.g., status updates, trip assignments)
  2. Simplicity: At-least-once is simpler to implement and debug than exactly-once
  3. 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:

  1. Domain Errors - Business logic violations (e.g., driver not available, location invalid)
  2. Infrastructure Errors - System-level issues (e.g., database connection failure, cache timeout)
  3. 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 TypeAlert ThresholdSeverity
Infrastructure Errors> 10 errors/minuteCritical
Domain Errors> 50 errors/minuteWarning
Validation Errors> 100 errors/minuteInfo

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 requests
    • ADMIN: Can manage drivers, view analytics, and override matches
  • Fine-grained permissions checked at service layer using @PreAuthorize annotations 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 timestamp
  • trace_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 | DEBUG
  • message: Human-readable log message
  • user_id: Optional, masked for PII (first 4 chars + ***)
  • driver_id: Optional, masked for PII (first 4 chars + ***)
  • request_id: Unique identifier for the incoming request
  • error_code: Application-specific error code (e.g., "DRIVER_NOT_FOUND")
  • error_stack: Optional, full stack trace for ERROR level logs
  • duration_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:

  1. driver_matching_service.match_driver - Main matching logic
  2. driver_matching_service.fetch_driver_location - Location fetch from DB
  3. driver_matching_service.publish_driver_status_event - Event publishing
  4. driver_matching_service.validate_request - Request validation

Span Attributes:

  • request_id: Unique request identifier
  • driver_count: Number of drivers considered in matching
  • match_distance_km: Distance threshold for matching
  • user_location.lat: User's latitude coordinate
  • user_location.lng: User's longitude coordinate

Metrics Design

RED Method Metrics:

Metric NameTypeLabelsDescription
driver_matching_requests_totalCounterstatus, error_codeTotal request count
driver_matching_errors_totalCountererror_type, operationError count by type
driver_matching_duration_secondsHistogramoperation, statusRequest processing time
driver_matching_active_requestsGaugeoperationCurrently active requests
driver_matching_cache_hit_ratioGaugecache_typeCache hit rates
driver_matching_queue_sizeGaugequeue_nameAsync 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_seconds p99 > 500ms (1 min window)
  • driver_matching_requests_total rate drops below 10 req/sec for 5 min

Warning Alerts:

  • driver_matching_duration_seconds p99 > 200ms (1 min window)
  • driver_matching_cache_hit_ratio < 70% (5 min window)
  • Database connection pool utilization > 85%

On-call escalation path:

  1. On-call engineer notified via Slack
  2. If no response within 5 min, escalate to primary team
  3. 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.

Was this article helpful?

Topics in this article

More from Backend LLD

  • Backend HLD

    API Rate Limiter Design and Implementation

    Comprehensive backend system design for rate limiting APIs with token bucket and sliding window algorithms, including distributed architecture considerations.

  • Backend LLD

    Trip State Machine Design for Ride-Sharing Platform

    Comprehensive low-level design for trip state management system with event-driven architecture and fault tolerance considerations.

  • Backend HLD

    Live Comment System Architecture Design

    Comprehensive backend high-level design for a scalable real-time comment system with pub/sub, caching, and conflict resolution mechanisms.

Stay in the Loop

Get curated DSA patterns, coding problems, frontend and backend LLD/HLD, and AI/ML System Design insights delivered straight to your inbox. No spam, just focused interview and engineering content.