Problem
Ride-hailing backends fail in expensive ways: a stuck PaymentIntent, a delayed webhook, a matching race, or a shared database lock that couples trip state to billing. The architecture has to make ownership obvious — especially for money.
Service map (verified shape)
Clients hit an api-gateway (Fastify) for REST. Domain work lives in NestJS services such as auth, rider, driver, trip, matching, pricing, payment, location, notification, and others. Realtime does not share fate with REST: a dedicated realtime-gateway fronts WebSocket traffic for location and trips.
One Postgres per service
Each service owns its Prisma/Postgres schema. There is no cross-service shared database. That forces explicit contracts (HTTP/events) instead of “just join the other table.”
RabbitMQ events
Domain fan-out uses a RabbitMQ topic exchange (current architecture — not Kafka). Example: trip.completed is consumed so payment-service can charge, then emit payment.completed for earnings. Notification and other consumers subscribe without trip-service knowing Stripe.
Payment ownership
payment-service owns all Stripe. Trip-service never calls Stripe directly. That boundary is the difference between a maintainable ledger and a distributed ball of processor errors.
Documented money rules that matter in production:
- Fares via PaymentIntents after trip completion
- Tips: drivers receive 100%; Stripe tip processing cost is a platform expense
- Cancel settlement is sealed before payment executes VOID / fee / partial capture paths
- Driver earnings are idempotent on trip identity
- Connect payouts (weekly schedule, Financial Connections, platform-collected KYC patterns)
Realtime isolation
Socket traffic hits realtime-gateway (Redis-backed Socket.IO patterns, long ALB idle timeouts). Location and trip fan-out stay off the REST gateway’s critical path.
RuntimeConfig
Operational knobs (fees, schedules, feature flags) live in RuntimeConfig so markets can change behavior without redeploying every service.
Trade-offs
| Choice | Why | Cost | |---|---|---| | Microservice + DB-per-service | Clear ownership, independent failure | Operational overhead, eventual consistency | | RabbitMQ | Ops-friendly for this stage | Throughput ceiling vs heavy Kafka shops | | Stripe only in payment-service | Money auditability | Extra event choreography | | Dedicated realtime gateway | Isolate WS load | Another deployable to run |
What this is not
This essay describes the platform architecture John works against and contributes to. It does not claim sole authorship of every payment or infra subsystem. Client-facing ownership is covered in Mobile architecture and the Rydvu case study.