Architecture
How to Architect a Real-Time Multi-Sided Platform
A layer-by-layer engineering guide to multi-sided products — matching, payments, real-time location, async workers, and the invariants that keep the system coherent under failure. Clust and similar mobility products are a worked example.
SystoBase Engineering · · 18 min
Key takeaways
- Start from domain lifecycle invariants, not from a framework shopping list.
- Separate hot location state (Redis GEO) from transactional domain state (PostgreSQL).
- Treat matching, payments, and notifications as distinct failure domains with explicit contracts.
- Industry pattern: monolithic API early; SystoBase recommendation: modular boundaries inside one deployable unit until team scale forces split.
- Observability is an architecture layer — if you cannot trace a domain ID across services, you cannot operate the product.
- Async work belongs off the request path, but only after queue isolation is designed.
More than two apps
A multi-sided real-time product looks simple from the outside: consumer apps, provider apps, and a live map. In production it is a distributed system coordinating identity, availability, matching, pricing, payments, maps, notifications, fraud checks, and operator tooling. Each concern has different latency, consistency, and recovery requirements. Ride-sharing and logistics platforms are familiar examples of the same shape.
Architecture conversations often collapse into technology choices — Django vs Node, Kafka vs Redis, microservices vs monolith. Those choices matter, but they are secondary to domain invariants: a job should not complete without a coherent payment path; a provider marked offline should not receive offers; location used for matching must have an explicit freshness definition.
Industry pattern: teams copy the surface area of large marketplace platforms without copying the operational maturity behind them. SystoBase recommendation: document the domain lifecycle as a state machine first, then map components to the states and transitions they own. Clust’s ride lifecycle is one production instance of that approach.
Core layers
Most multi-sided real-time platforms converge on a familiar stack: mobile clients, an API boundary, authentication, domain services, matching, real-time delivery, async workers, relational data, external providers, and infrastructure. The value is not inventing new layers — it is defining ownership, SLAs, and failure modes between them.
Mobile clients encode product workflow and optimistic UI. They should not embed business rules that must be enforced server-side — especially around pricing, eligibility, and state transitions. The API layer is the trust boundary: authentication, authorization, rate limits, idempotency keys, and input validation live here.
Matching owns candidate selection and assignment. Real-time systems own freshness of location and live updates to clients. Async workers own fan-out, notifications, reconciliation, and anything that can tolerate seconds of delay. Keeping these roles explicit prevents “everything calls PostgreSQL” anti-patterns that collapse under concurrent supply density.
- Clients: workflow and presentation, not source of truth
- API: trust boundary, idempotency, and contract stability
- Matching: candidate selection, assignment, and concurrency control
- Real-time: hot location and live fan-out
- Workers: notifications, reconciliation, analytics pipelines
Hot location vs transactional state
Provider location at matching time is read-heavy, updated frequently, and tolerates brief staleness within defined bounds. Job records are write-heavy around lifecycle transitions and require ACID guarantees when money and assignment are involved. Collapsing both into one storage model usually fails as concurrency grows.
Industry pattern: write every GPS ping to PostgreSQL and query with PostGIS. That works at modest scale and keeps operational simplicity. SystoBase recommendation: use Redis GEO for online provider positions updated every few seconds, and PostgreSQL for jobs, users, payments, and audit history. Sync boundaries should be explicit — matching reads hot location; domain state commits transactionally.
PostGIS remains valuable for spatial analytics, geofencing validation, and historical route analysis — not necessarily for every sub-second matching read. When you outgrow naive nearby queries, index design and bounded search radii matter more than switching databases prematurely.
The matching boundary
Matching is where product rules meet concurrency. Two customers near the same corner should not assign the same provider because two API handlers raced. Assignment must be atomic relative to availability, with timeouts and clear recovery when providers decline or disconnect.
Industry pattern: geospatial query returns nearest N providers, pick the first free one. SystoBase recommendation: treat assignment as a short-lived lease — acquired in a transaction or compare-and-set against availability version — with automatic release on timeout. Matching quality (ETA, rating, capability) sits on top of that safety layer.
Matching failures show up as ghost jobs, duplicate assignments, and “en route” when nobody accepted. Monitor assignment latency, decline rates, and the gap between “offer sent” and “offer acknowledged”.
Payments and async workers
Payments intersect every completed billable job. Authorization, capture, refunds, and wallet movements must align with domain state transitions. Partial success — money captured but job stuck in started — is a production failure mode, not an edge case.
Industry pattern: fire-and-forget Celery tasks after the HTTP response. SystoBase recommendation: separate queues by blast radius — job-critical tasks on dedicated workers; bulk exports and marketing on isolated queues with lower priority. Redis as broker is common; queue depth and task age are mandatory metrics.
Webhooks from payment providers must be verified, idempotent, and reconciled against internal state. Never assume a single synchronous API call finalizes both sides.
Failure domains and monitoring
Split the system mentally by what breaks independently: mobile network, API pods, Redis, PostgreSQL, Celery workers, payment provider, maps provider. Each domain needs health checks, RED metrics (rate, errors, duration), and trace correlation on a shared job or order ID.
Industry pattern: dashboard per service without cross-cutting traces. SystoBase recommendation: structure logs and spans around job_id (or order_id) from the first request through matching, location updates, completion, and capture. Alert on invariant violations — completed jobs without payment intent, assignments older than threshold, queue age for critical tasks.
Runbooks should name the first three queries an engineer runs during an incident. Architecture is incomplete without that operational layer.
Where to start
For an MVP, prioritize a correct domain state machine, trustworthy availability, idempotent payment hooks, and enough observability to debug production. Scale optimizations — sharded location, multi-region — come after demand proves the product.
If you inherit an existing platform, map the live path of one completed job before proposing rewrites. Architecture advice without a traced production path is speculation. SystoBase teams typically begin with invariant review, queue composition, and matching concurrency — those three areas predict most early outages.