Ride Hailing Platform
Match riders and drivers under location churn and acceptance races.
Product overview
Match riders with eligible nearby drivers, maintain a trustworthy trip state machine, and price and settle completed transportation under high location churn.
Main actors
Rider, Driver, Safety operator, Support.
Assumptions
- Drivers are independently available or unavailable
- A maps provider supplies routes
- Pricing rules are versioned
- Safety and licensing obligations vary by jurisdiction
Non-goals
- Autonomous driving
- Building maps
- Replicating any proprietary dispatch algorithm
- Cash handling
Requirements
Must-have
- Driver availability and location ingestion
- Expiring fare quote
- Race-safe matching
- Ordered trip transitions
- Final fare and receipt
Should-have
- Scheduled rides
- Safety escalation
- Driver destination filters
- Cancellation fees
Future scope
- Shared rides
- Multi-region roaming
- Fleet accounts
- Demand prediction
Non-functional decisions
| Concern | Decision |
|---|---|
| Latency | Candidate lookup and offer delivery are interactive; final settlement can converge asynchronously. |
| Consistency | A trip has one assigned driver and ordered states. Location, ETA, and heat maps are eventual. |
| Availability | Never create ambiguous assignment to improve availability; degrade maps and pricing explicitly. |
| Privacy | Exact trip location is purpose-limited, access-logged, and retained according to policy. |
| Auditability | Price rule/version, route inputs, safety access, and manual changes are preserved. |
User roles
| Role | Access boundary |
|---|---|
| Rider | Own quotes, trips, receipts |
| Driver | Own availability, offers, assigned trips, earnings |
| Safety operator | Break-glass trip access with immutable audit |
| Support | Masked trip and payment view; scoped adjustments |
Core workflows
Domain model
| Entities | Relationship |
|---|---|
Driver → DriverStatus | Availability is durable; positions are ephemeral |
RideQuote → PricingVersion | Quote fixes inputs and expiry |
Trip → DriverOffer | Many offers, no more than one winner |
Trip → Fare | Final fare records components and quote relationship |
Trip → SafetyIncident | Restricted incident record and audit trail |
Database design
PostgreSQL remains the transactional source of truth. Add specialized stores only for the workloads called out below.
trips — Authoritative ride workflow
driver_status — Durable eligibility state
ride_quotes — Expiring price promise
driver_offers — Dispatch attempts
Transaction and retention boundaries
- Accept offer by locking/conditionally updating both trip and driver availability
- Trip transition plus outbox is atomic
- Final fare record and balanced earnings entries share a commit
- Ephemeral raw locations expire; financial/trip audit records use retention states, not ad-hoc delete
API design
| Endpoint | Purpose and permission | Idempotency / status |
|---|---|---|
POST /v1/ride-quotes | Price a route with an expiry Rider | Not required 201, 422, 503 |
POST /v1/trips | Request a ride from a quote Rider | Required 201, 409, 422 |
POST /v1/driver-offers/:id/accept | Attempt trip assignment Offer recipient | Required 200, 409, 410 |
PATCH /v1/trips/:id/status | Advance allowed state with version Assigned driver or restricted operator | Transition id 200, 409, 422 |
POST /v1/drivers/me/locations | Ingest sequenced location batch Authenticated eligible driver | Not required 202, 400, 429 |
GET /v1/trips/:id | Read authorized trip projection Trip rider/driver or audited staff | Not required 200, 403, 404 |
List endpoints use opaque keyset cursors unless a small bounded collection is explicit. Unknown objects return 404 where 403 would leak existence.
Architecture
System context
Main application architecture
Critical workflow sequence
Event model
| Event | Producer → consumers | Delivery |
|---|---|---|
TripRequested | Trip API → Matcher | Outbox; matcher deduplicates trip/version |
DriverAssigned | Matcher → Realtime, notifications | At-least-once; versioned projection |
TripCompleted | Trip API → Fare, settlement, safety analytics | Inbox dedupe; settlement dead-letter is operationally visible |
Failure modes
Acceptance race
Failure
Two drivers accept near-simultaneously.
Mitigation
Conditional trip and driver updates in one transaction; only committed winner succeeds.
Stale location
Failure
Nearest candidate is no longer nearby.
Mitigation
Filter by sample age, show offer expiry, and expand search rings.
Out-of-order GPS
Failure
Mobile batches arrive late.
Mitigation
Per-device sequence/timestamp rejects older live projection while retaining bounded telemetry.
Completion timeout
Failure
Trip completes but response is lost.
Mitigation
Transition id returns stored outcome; fare creation is unique per trip.
Maps outage
Failure
No route or ETA is available.
Mitigation
Stop new quotes or use clearly bounded fallback policy; active trips remain operable.
Driver disappears
Failure
Assigned driver goes offline before pickup.
Mitigation
Heartbeat timeout marks attention-needed; safe reassignment uses explicit cancellation transition.
Consistency and concurrency
Strong consistency / transactions
- One trip-driver assignment
- Driver cannot hold incompatible active trips
- State ordering
- Fare and earnings
Eventual convergence
- Position
- ETA
- supply heat map
- notifications
Concurrency controls
- Trip assignment is a database compare-and-set, not a distributed lock
- Lock trip then driver in stable order
- Unique active-trip constraint protects driver eligibility
- Monotonic location sequence controls reordering
Caching and background work
Safe to cache
- Map tiles
- Pricing configuration by version
- Coarse supply heat map
Never a cache authority
- Assignment winner
- Current trip state for writes
- Safety authorization
Versioned config is long-lived; heat maps use seconds-long TTL. Live reads include freshness and writes always hit the owner.
Background jobs
- Expire quotes/offers
- Expand match radius
- Detect missing heartbeats
- Calculate fare
- Reconcile capture
- Apply location retention
Security boundaries
Security claims here are design controls, not certification. Threat modeling and jurisdiction-specific review remain required.
- Trip object authorization for rider and assigned driver
- Short-lived scoped driver device tokens
- Minimize exact location exposure and audit staff access
- Server-verifiable state transitions and anti-spoofing signals
- Rate limits on location and ride requests
- Signed payment webhooks and hosted tokenization
- Step-up access for safety tooling
Observability
Logs
- Trip and driver state transitions with actor, expected version, and rejection reason
- Match attempt ring, candidate count, offer expiry, and assignment winner
- Location ingestion sequence gaps and dropped stale samples without logging precise coordinates
Metrics
- Quote latency, match latency, and unassigned-trip age by city
- Offer acceptance, assignment conflict, and driver cancellation rates
- Driver location freshness and ingestion rejection rate
- Illegal trip-transition attempts and fare reconciliation exceptions
Traces
- Trace ride request from quote through geo lookup, offer delivery, and assignment CAS
- Trace trip completion through fare calculation, capture, and earnings posting
Alerts
- Match latency or unassigned backlog breaches city SLO
- Fresh eligible-driver supply drops unexpectedly
- Completed trips remain unpriced or uncaptured
Testing strategy
| Layer | What it proves |
|---|---|
| Unit | Trip state machine, quote expiry, pricing rules, sequence handling |
| Integration | PostGIS lookup, atomic assignment, outbox, fare uniqueness |
| Contract | Map/payment adapters and mobile event versions |
| Concurrency | Driver/trip cross-races and duplicate transitions |
| Failure | Offline devices, delayed GPS, map timeout, capture ambiguity |
| Load | Geographically skewed location writes and stadium dispatch burst |
Do not mock PostgreSQL constraints, transaction isolation, object-store semantics, or provider contract fixtures in the tests intended to prove those boundaries.
Deployment evolution
Scale evolution
These are architecture scenarios, not throughput claims. Each transition should follow measured workload and reliability pressure.
| Scenario | Architecture may evolve toward | Decision rule |
|---|---|---|
| 1K users | One API, PostgreSQL with PostGIS for trips, an in-memory geo index of available drivers, a matching worker | Assign a driver with a conditional update; stale driver positions are expected, double assignment is not |
| 100K users | Separate high-write location path; shard matcher by city/geocell; keep trip writes on PostgreSQL owner | Shard matching by city or geocell when match latency or location freshness degrades; each trip keeps a single owner |
| 1M+ users | Regional cells own matching and trips; event streams feed cross-region analytics without making them assignment authorities | A regional cell owns matching and trip state for its area; cross-region streams feed analytics, never assignment |