The system skeleton
- Name the nine services of the exchange and what each owns.
- Explain the two traffic planes — request/response and real-time streaming — and why they are separated from day one.
From 0.2: exchange core vs. supporting vs. platform. From 0.3: 250 K location writes/s vs. 1 K trip requests/s — a 250:1 ratio. That ratio is why the skeleton has two planes.
We decompose by ownership of state, not by team size or fashion: a service exists where a distinct dataset with distinct consistency needs exists. Nine services result. Each owns its data; no service reads another's tables — at this tier they may still share one physical database cluster, but schemas are private and all cross-service access goes through APIs. That discipline is cheap now and is precisely what makes Parts 2–3 possible without rewrites.
Traffic splits into two planes with wildly different shapes. The request plane (HTTPS, request/response) carries logins, quotes, ride requests, payments — low rate, high value. The streaming plane (WebSocket/gRPC streams) carries driver location pings and live trip updates — 250× the rate, tolerant of a lost message because the next ping arrives in 4 seconds. Mixing them would force the chatty plane's scaling and failure profile onto the valuable one.
flowchart TB
RA["📱 Rider app"] --> LB["Load balancer + TLS"]
DA["🚗 Driver app"] --> LB
LB --> GW["API gateway — authn, rate limits, routing"]
DA -. "location stream" .-> WS["Realtime gateway — WebSocket"]
RA -. "live map + trip updates" .-> WS
subgraph CORE ["Exchange core"]
LOC["Location service — driver presence, geo index"]
MATCH["Matching service — dispatch"]
TRIP["Trip service — lifecycle state machine"]
PRICE["Pricing service — quotes, surge"]
end
subgraph SUPP ["Supporting services"]
ID["Identity service"]
PAY["Payments service + ledger"]
NOTIF["Comms service — push, SMS, chat"]
RATE["Ratings and history"]
SUPT["Support and admin"]
end
GW --> ID
GW --> PRICE
GW --> TRIP
GW --> MATCH
GW --> PAY
GW --> RATE
GW --> SUPT
WS --> LOC
MATCH --> LOC
MATCH --> TRIP
PRICE --> LOC
TRIP --> PAY
TRIP --> NOTIF
MATCH --> NOTIF
subgraph DATA ["State"]
PG[("PostgreSQL — system of record")]
RD[("Redis — geo index + cache")]
Q[["Message queue — async jobs"]]
S3[("Object store — documents, receipts")]
end
CORE --> PG
SUPP --> PG
LOC --> RD
SUPP --> Q
CORE --> Q
The classic error at this stage is premature microservices: forty services, each 99.9%, chained serially — recall 0.3, you've built a 96% system with extra steps. Nine services with private state and shallow call depth is the disciplined middle.