Skip to content

EngineeringBackend architecture — service boundaries that survive money and trips

Engineering essay

Backend architecture — service boundaries that survive money and trips

How Rydvu’s Nest monorepo separates trip, payment, location, and matching so failure modes do not collapse into one process.

NestJS microservices for Rydvu — one database per service, RabbitMQ events, payment-service Stripe ownership, and a realtime gateway isolated from REST.

  • backend
  • microservices
  • nestjs
  • payments
  • rabbitmq

Published

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.

Related work