Marketplace / Two-sided — Designed in Stages
You don’t need to design for scale on day one.
Define what you need—listings, search, order flow, and payments—then build the simplest thing that works and evolve as buyers, sellers, and transaction volume grow.
Here we use a two-sided marketplace as the running example: buyers, sellers, listings, orders, and reviews. The same staged thinking applies to any platform that matches supply and demand: search, trust, and payments are central.
Requirements and Constraints (no architecture yet)
Section titled “Requirements and Constraints (no architecture yet)”Functional Requirements
- Listings — sellers create and manage listings (product, service, or offer); CRUD, status (draft, active, sold), and visibility.
- Search and discovery — buyers search or browse listings; filters (category, price, location); ranking (relevance, recency, or popularity).
- Order flow — buyer commits to a listing (purchase, booking, or request); order is created with status; seller confirms or fulfills; both sides see order state.
- Payments — payment is collected (e.g. at checkout or on fulfillment); idempotent payment calls; may hold in escrow or split between platform and seller; integrate with Payments example for consistency.
- Trust — reviews and ratings (buyer on seller, or both ways); reputation or trust score; optional dispute flow.
Quality Requirements
- Two-sided — both buyers and sellers are first-class; notifications and workflows for both; balance supply and demand (matching, discovery).
- Search — listing search is read-heavy; latency and relevance matter; simple DB query for MVP, dedicated search index as scale grows.
- Payments — strong consistency and audit (see Payments example); idempotency for payment calls; reconciliation between platform and payment provider.
- Expected scale — listings count, orders per day, search QPS, number of sellers and buyers.
Key Entities
- Buyer — user who browses and purchases; has profile, payment method, order history.
- Seller — user who creates listings; has profile, payout details, ratings.
- Listing — offer (product/service); title, description, price, category, seller_id, status; searchable and filterable.
- Order — commitment from buyer to a listing; order_id, buyer_id, seller_id, listing_id, amount, status (pending, confirmed, fulfilled, cancelled); payment and fulfillment state.
- Review — rating or review (e.g. buyer reviews seller after order); affects reputation.
Primary Use Cases and Access Patterns
- Listings CRUD — create, read, update, delete listing; list “my listings” (seller); list by category or search (buyer).
- Search listings — full-text or filtered search; dominant read path; must be fast and relevant.
- Order flow — create order (buyer), confirm/cancel (seller or system), update status; idempotent payment call at appropriate step.
- Notifications — notify buyer (order confirmed, shipped) and seller (new order, payment received); can start with email or in-app, add push later.
Given this, start with the simplest MVP: one API, one DB, listings CRUD, simple search (DB or basic index), order flow, and payments via idempotent calls to a payment provider, then evolve with a search index, notifications, and payment reconciliation as volume grows.
Stage 1 — MVP (simple, correct, not over-engineered)
Section titled “Stage 1 — MVP (simple, correct, not over-engineered)”Goal
Ship a working marketplace: sellers can create listings, buyers can search and place orders, and payment is collected reliably. One API, one DB, minimal moving parts.
Components
- API — REST or GraphQL; auth (buyer and seller); listings CRUD; search or list listings (simple query or basic index); create order; payment endpoint (or call to payment provider with idempotency key).
- Primary DB — stores users (buyers, sellers), listings, orders, and optionally reviews; indexes for listing search (e.g. category, status, created_at) and for “my orders” / “my listings.”
- Simple search — listing search implemented as DB query (filter + sort) or a basic full-text index (e.g. DB full-text or minimal Elasticsearch); enough for small-to-medium catalog.
- Order flow — create order row (pending); call payment provider (idempotent); on success, update order to confirmed and optionally notify seller; seller marks fulfilled; both can view order history.
- Payments via idempotent calls — single payment call (or redirect to provider) with idempotency key; store outcome (transaction id, status) in DB; retries don’t double-charge; see Payments example for details.
Minimal Diagram
Buyer Seller | | v v+-----------------+| API |+-----------------+ | | v vPrimary DB (listings, orders, users) | | | v | Payment provider (idempotent call) vSimple search (DB query or basic index)Patterns and Concerns (don’t overbuild)
- Auth and roles: distinguish buyer vs seller (or same user can be both); authorize “create listing” for seller, “create order” for buyer.
- Order state machine: define states (e.g. pending, confirmed, fulfilled, cancelled); transitions and who can trigger them; avoid invalid states.
- Idempotency for payment: pass idempotency key (e.g. order_id) to payment provider; store result in order row; on retry, return stored result.
- Basic monitoring: listing count, order rate, payment success/failure, search latency.
Why This Is a Correct MVP
- One API, one DB, simple search, order flow, idempotent payments → two-sided flow works end-to-end; easy to reason about and operate.
- Vertical scaling and DB indexing buy you time before you need a dedicated search index and service split.
Stage 2 — Growth Phase (more listings, search load, notifications)
Section titled “Stage 2 — Growth Phase (more listings, search load, notifications)”What Triggers the Growth Phase?
- Listing search becomes slow or inadequate (complex filters, full-text relevance); DB query or basic index doesn’t scale.
- Buyers and sellers need timely notifications (new order, payment, fulfillment); email or in-app only may not be enough.
- Payment volume grows; need reconciliation (platform records vs payment provider) and possibly payout workflow for sellers.
- Hot listings (e.g. popular items) cause read hotspots; cache can reduce DB load.
Components to Add (incrementally)
- Search index for listings — sync or async index listings into a search engine (e.g. Elasticsearch/OpenSearch); search API queries index instead of (or in addition to) DB; supports full-text, facets, and ranking.
- Notifications (buyer and seller) — notify on order created, payment received, order fulfilled, etc.; start with email or in-app; add push (mobile/web) and preference center as needed; use queue + worker to avoid blocking order flow.
- Payment reconciliation — periodic job (or stream) that compares platform order/payment records to payment provider data; flag discrepancies; support dispute resolution and financial close.
- Cache for hot listings — cache popular or recently viewed listings (e.g. by listing_id or by category); TTL and invalidation on update; reduces DB or search load for hot keys.
Growth Diagram
+------------------+Buyers / Sellers -> | API | +------------------+ | | | | v v v v Primary DB Search Index Cache (hot) | ^ | | index sync v | Order flow Listings | v Payment provider (idempotent) | v Notifications (queue + worker) | v Reconciliation job (orders vs provider)Patterns and Concerns to Introduce (practical scaling)
- Eventual consistency for search: listing updates (create, edit, sold) are reflected in search index after sync or async indexing; define max staleness (e.g. seconds to minutes).
- Notification delivery: use queue so sending doesn’t block API; retry and dead-letter for failures; respect user preferences (opt-out, channel).
- Reconciliation: idempotent runs; store reconciliation results and alerts; human review for mismatches.
- Monitoring: search latency and index freshness, notification delivery rate, reconciliation drift, cache hit ratio.
Still Avoid (common over-engineering here)
- Splitting into many microservices (listings service, orders service, etc.) before boundaries and bottlenecks are clear.
- Complex reputation or trust pipeline (e.g. real-time score) before you have fraud or quality problems.
- Multi-region marketplace before you have geographic demand.
Stage 3 — Advanced Scale (service split, trust, multi-region)
Section titled “Stage 3 — Advanced Scale (service split, trust, multi-region)”What Triggers Advanced Scale?
- Single API or DB becomes the bottleneck; clear ownership boundaries (listings vs orders vs payments) justify separate services.
- Trust and safety become critical: fraud, fake reviews, seller reliability; need reputation pipeline and possibly moderation.
- Buyers and sellers in multiple regions; need low latency and possibly local compliance (tax, payout).
Components (common advanced additions)
- Service split — separate services (or bounded contexts) for listings, search, orders, and payments; each with its own store or primary; communicate via API or events; keep order and payment consistency (saga or outbox as in Payments example).
- Reputation / trust pipeline — aggregate reviews and ratings; compute seller (and optionally buyer) reputation score; surface in search ranking and buyer UI; detect anomalies (fake reviews, fraud) and flag for review.
- Multi-region (if needed) — deploy API and data in multiple regions; route users to nearest region; replicate or partition listings and orders; payments and compliance may be region-specific (tax, payout rails).
Advanced Diagram (conceptual)
+------------------+ | API Gateway / LB | +------------------+ | | | | +-----------+ | +-----------+ v v v Listings Service Search Service Orders Service | | | v v v Listings DB Search Index Orders DB | ^ | | | v +----------------+ Payments Service | v Payment provider | +--------------------------------+----------------+ v v v Reputation pipeline Notifications Reconciliation (reviews -> score, fraud) (multi-channel) (cross-region)Patterns and Concerns at This Stage
- Consistency across services: order creation may span listings (check availability), orders (create order), and payments (charge); use saga or outbox and idempotency so failures don’t leave inconsistent state.
- Reputation: batch or near-real-time aggregation; avoid gaming (e.g. only count verified purchases); store score and history for audit.
- Multi-region: define data residency (listings global vs regional); orders and payments may be regional for compliance; replication and conflict handling if same listing is updated in multiple regions.
- SLO-driven ops: order success rate, search latency, payment success, notification delivery; error budgets and on-call.
Summarizing the Evolution
Section titled “Summarizing the Evolution”MVP delivers a two-sided marketplace with one API, one DB, simple search, order flow, and idempotent payments. That’s enough to ship and learn.
As you grow, you add a search index for listings, notifications for buyers and sellers, payment reconciliation, and cache for hot listings. You keep the order and payment path consistent and auditable.
At very high scale, you split into services (listings, search, orders, payments), add a reputation/trust pipeline, and consider multi-region for latency and compliance. You add complexity only where ownership, trust, or geography require it.
This approach gives you:
- Start Simple — one API, one DB, simple search, order flow, idempotent payments; ship and learn.
- Scale Intentionally — add search index and notifications when discovery and engagement justify it; add service split and trust when boundaries and safety demand it.
- Add Complexity Only When Required — avoid microservices and reputation pipeline until bottlenecks and product needs are clear.