DEV Community

Ratnesh Maurya
Ratnesh Maurya

Posted on • Originally published at blog.ratnesh-maurya.com on

Architectural Design for a Ride App such as OLA, UBER, RAPIDO

Architectural Design for a Ride App such as OLA, UBER, RAPIDO

A ride-sharing app needs to match riders with drivers in real-time, track locations, process payments, and handle all of this at scale across a city. This is my take on how to architect such a system using microservices — and where a simpler approach might actually be better.

Why microservices for a ride app

The core challenge is that different parts of the system have very different scaling requirements:

  • Geo-location tracking needs to handle thousands of location updates per second with sub-100ms latency
  • Payment processing needs strong consistency and idempotency but handles far fewer requests
  • Notifications are fire-and-forget at high volume
  • User authentication is read-heavy with infrequent writes

A monolith would force all of these to scale together. Microservices let you scale the geo-location service to 50 instances while keeping the payment service at 3.

That said, if you're building an MVP or serving a single city, start with a monolith. Uber itself started as a monolith. Extract services only when specific components hit scaling bottlenecks.

Service boundaries

Each service owns its data and exposes a clear API:

User Service — Manages rider and driver accounts, authentication (OAuth2/OIDC), profile data, and session management. Backed by PostgreSQL.

Ride Service — The core coordination service. Handles ride requests, matches riders with nearby available drivers, tracks ride state (requested → matched → in-progress → completed), and calculates fares. Backed by PostgreSQL with Redis for active ride state.

Driver Service — Manages driver availability, approval status, vehicle information, and earnings. Tracks which drivers are online, idle, or on a ride. Backed by PostgreSQL.

Geo-Location Service — The highest-throughput service. Receives GPS coordinates from driver and rider apps every 3–5 seconds, stores them in a spatial index (Redis with geospatial commands or a dedicated service like H3), and answers proximity queries ("find the 5 nearest available drivers within 3km"). This is the service that needs the most aggressive scaling.

Payment Service — Integrates with payment gateways (Razorpay, Stripe). Processes charges after ride completion, handles refunds, and manages driver payouts. Must be idempotent — a network retry should never charge a rider twice.

Notification Service — Sends push notifications (ride matched, driver arriving, ride completed), SMS fallbacks, and email receipts. Consumes events from a message queue rather than being called directly.

How the services communicate

Not all communication should use the same pattern:

Pattern Use case Why
REST/HTTP Client → API Gateway → Services Simple request/response for CRUD operations
gRPC Service-to-service (e.g., Ride Service → Geo-Location Service) Low latency, typed contracts, streaming support
Message queue (Kafka/RabbitMQ) Ride Service → Notification Service, Ride Service → Analytics Async, decoupled, no backpressure on the producer

The API Gateway sits in front of all services and handles routing, rate limiting, and authentication token validation. Clients never talk directly to internal services.

A critical design decision: the Ride Service publishes a ride.completed event to Kafka. The Payment Service, Notification Service, and Analytics Service all consume this event independently. This means adding a new consumer (say, a driver-rating prompt) doesn't require changing the Ride Service at all.

Scaling and fault tolerance

Load balancing. An L7 load balancer (like AWS ALB or Envoy) distributes requests across service instances. The Geo-Location Service gets its own load balancer with sticky sessions disabled (since requests are stateless).

Auto-scaling. Kubernetes Horizontal Pod Autoscaler watches CPU and custom metrics (like queue depth for the Notification Service). The Geo-Location Service might scale from 10 pods at 2 AM to 80 pods at 6 PM during rush hour.

Circuit breakers. If the Payment Service is down, the Ride Service shouldn't hang waiting for it. A circuit breaker (e.g., via Istio or a library like resilience4j) fails fast after N consecutive errors and falls back to queuing the payment for later processing.

Health checks. Every service exposes /healthz (liveness) and /readyz (readiness) endpoints. Kubernetes restarts crashed pods automatically and stops routing traffic to pods that aren't ready.

Security considerations

  • Authentication: OAuth2 with JWTs for user-facing APIs. Service-to-service calls use mTLS within the Kubernetes cluster.
  • Data encryption: AES-256 at rest, TLS 1.3 in transit. Payment card data never touches your services — use the payment gateway's tokenization.
  • Rate limiting: Applied at the API Gateway level. Geo-location updates are rate-limited per device to prevent abuse.
  • Secrets management: HashiCorp Vault or AWS Secrets Manager. No secrets in environment variables or config files.

Deployment

  • Containerization: Each service is a Docker image with a multi-stage build (builder → runtime). Images are scanned for vulnerabilities in CI.
  • Orchestration: Kubernetes with namespaces per environment (dev, staging, prod). Services declare resource requests and limits.
  • CI/CD: GitHub Actions or GitLab CI runs tests, builds images, pushes to a container registry, and deploys via Helm charts or ArgoCD.
  • Observability: Prometheus + Grafana for metrics, Jaeger for distributed tracing, Fluentd/Loki for centralized logs. Every service emits structured JSON logs with a correlation ID.

What I'd do differently at different scales

Scale Approach
MVP (1 city, < 1K rides/day) Monolith with a single PostgreSQL database. Extract the geo-location queries into a background worker if they slow down the main app.
Growth (5 cities, 10K rides/day) Extract Geo-Location and Notification as separate services. Keep everything else in the monolith. Add Redis for caching.
Scale (50+ cities, 100K+ rides/day) Full microservices as described above. Invest in Kafka for event-driven architecture, dedicated SRE team, and per-service databases.

The biggest mistake is building for the third row when you're at the first row. Start simple, measure, and extract when the pain is real.

Top comments (0)