Skip to main content

E-Commerce Microservices

Cloud-native e-commerce platform on AWS

synchronous: caller waits
asynchronous: fire & forget

Denser dots = higher throughput. Hover any edge for its full contract.

//The Contract

Requirements

Functionalwhat the system must do
  • A shopper can browse the catalog, search for products, and add items to a cart across web and mobile clients
  • A shopper can check out, and the order is created, charged, and confirmed without re-entering payment details mid-flow
  • A logged-in user's profile and session persist across devices without re-authenticating on every page
  • The order moves through a visible lifecycle — pending, paid, or payment_failed — and the shopper is notified of the outcome by email
  • Product availability shown during checkout reflects real stock, so two shoppers can't both buy the last unit
Non-functionalhow well it must do it
  • Checkout is revenue-critical — a payment-processor slowdown must degrade gracefully (orders stay pending), never take the whole platform down
  • The read path (browsing, search) must scale independently of the write path (checkout), since browsing outnumbers checkout roughly 500:1
  • Elastic capacity for flash-sale traffic spikes — checkout QPS can jump far past baseline within minutes
  • Defense-in-depth at the network edge, since payment and personal data flow through every layer between the client and Payment Service
  • Confirmation emails and event processing must never add latency to the checkout response
//Back of the Envelope

Scale Estimates

Rough, order-of-magnitude numbers. The point is to justify the architecture, not to be exact.

Daily active shoppers
5M
drives read/browse traffic across the storefront
Catalog size
50M SKUs
too large and too textual for relational LIKE queries
Browse : checkout ratio
500 : 1
browsing dwarfs writes — the read path is what must scale
Orders per day
1M/day
~12 orders/sec average, spiking far higher on sale days
Peak checkout QPS
~2K/s
flash-sale peak — justifies rate limiting and idempotent charges
Product search p95
<150ms
slow search pages measurably hurt conversion
//Why It Looks Like This

System Evolution

No system is born with queues and caches. Every box was added because something was about to break. Play a stage and the diagram above shrinks to what existed at that point in the story.

01

Core services — one gateway, four services, one database each

Every request already passes through DNS → CloudFront → WAF → ALB before reaching the API Gateway — even a new storefront assumes this baseline network posture. From there the gateway routes to whichever business service owns the request: User, Product, or Order. Order Service calls Payment Service directly to charge the card and gets a synchronous answer back, which is what lets checkout resolve immediately instead of guessing.

This is a real, working store — but every read, popular or not, round-trips to Postgres, and there's no way to search the catalog beyond exact lookups.

02

Cache the hot reads and split off a read replica

Scale pressure: Session lookups and order-status checks were hammering Postgres on every request, and profile reads competed with registration/profile-edit writes on the same primary.

A Redis Cluster goes in front of User, Product, and Order Service for their hottest reads, and User DB gets a read replica dedicated to profile lookups. Neither change touches correctness — a cache miss or a replica-lag read both still fall back to the authoritative database.

Browsing is fast again, but the catalog still can't do full-text search, and product images are proxied through the app tier instead of a dedicated store.

03

Add full-text search and offload static assets

Scale pressure: 50M SKUs of free-text, typo-tolerant search was straining Postgres LIKE queries, and CloudFront was fetching product images through Product Service instead of a dedicated object store.

Elasticsearch joins as a read-only, rebuildable index of Product DB, built for ranking and full-text queries — never the source of truth for price or stock. Product images move to S3, with CloudFront caching them at the edge and falling back to S3 on a miss.

Browsing and search are both fast now, but every order confirmation email still blocks on Notification logic living inline, and nothing decouples order-state changes from downstream consumers.

04

Decouple notifications with an event bus

Scale pressure: Order and payment state changes had no way to reach downstream consumers without those consumers slowing down checkout itself.

Order Service and Payment Service now publish Order Events and Payment Events onto an Event Bus (Kafka) without waiting for a response, and a standalone Notification Service consumes them to send confirmation, failure, and shipping emails through SendGrid.

This is the complete system: the checkout request only ever waits on the hops that must block, and everything else — search, email, analytics-adjacent work — happens off to the side.

//Follow the Data

Request Flows

A diagram shows what exists; a trace shows what happens. Press play on a flow, or click any step, and the diagram above lights up the exact path that request takes.

Flow 01

Browse and search the catalog

From a client request to ranked search results and rendered product images.

Flow 02

Create an order and charge payment

From tapping "Place Order" to a confirmed, paid order and a confirmation email.

Flow 03

Handle a declined payment

The failure path when the card issuer says no.

//Component Breakdown

Key Components

Every box on the canvas, and the job it does. Click a card to locate it in the diagram; click the node itself for the full deep dive.

client×2
network×5
compute×5
database×5
cache×1
queue×1
storage×1
external×1
//Deep Dive

Architecture Breakdown

Overview

This diagram represents a cloud-native e-commerce platform built on AWS using a microservices architecture. Each business capability — users, products, orders, payments, notifications — is deployed as an independent service with its own dedicated data store. This design enables teams to develop, deploy, and scale services independently.

Request Flow

Traffic flows through a layered network stack before reaching the application services:

1. DNS (Route 53) resolves the domain and routes requests to CloudFront (CDN) for edge caching. 2. CloudFront forwards to WAF (Web Application Firewall) for security filtering. 3. Clean traffic reaches ALB (Application Load Balancer) which distributes across targets. 4. API Gateway receives all requests and routes them to the appropriate internal service via gRPC.

This multilayered approach provides defence in depth — each layer adds caching, security, or routing capabilities without coupling to business logic.

Service Decomposition

User Service Handles authentication, authorization, and user profiles. It owns a primary PostgreSQL database with a read replica for scaling read-heavy workloads like profile lookups. This is a common pattern — separate read replicas absorb query traffic without competing with write capacity.

Product Service Manages the catalog and inventory. It stores product data in PostgreSQL, caches frequently accessed data in Redis, writes static assets (images) to S3, and indexes product data in Elasticsearch for full-text search. This showcases the database-per-service pattern where a single service may own multiple storage types for different concerns.

Order Service Orchestrates the order lifecycle — creation, payment, and fulfilment. It writes to a sharded DynamoDB table for horizontal scalability and emits Order Events to the Event Bus (Kafka) whenever an order changes state. This decouples order processing from downstream consumers.

Payment Service Processes payments by integrating with an external payment processor. Order Service calls it synchronously to charge the card — the shopper needs a definitive answer before checkout resolves — and it calls back to confirm the order's new status, while also publishing Payment Events to the event bus for downstream consumers like Notification Service.

Notification Service Consumes events from the Event Bus and sends emails via SendGrid. By subscribing to events rather than being called directly, it can be scaled independently and doesn't block the critical checkout path.

Data Tier

All data services are grouped under a data-tier boundary:

  • PostgreSQL: User DB (with read replica), Product DB — relational data with ACID guarantees
  • DynamoDB: Order DB (sharded) — NoSQL for horizontal scaling
  • Redis: Session cache and shared state across services
  • Elasticsearch: Full-text search indexing
  • S3: Static asset storage for product images and content
  • Kafka: Event bus for asynchronous communication between services

Key Architectural Decisions

  • gRPC over REST for inter-service communication — binary protocol with better performance and strong typing via protocol buffers
  • Event-driven for order and payment fan-out — services don't wait for responses from downstream consumers
  • Read replicas for read-heavy workloads — keeps the primary database focused on writes
  • Sharded DynamoDB for the order service — anticipates high write throughput as the platform scales

Trade-offs

This architecture prioritizes scalability and team autonomy over simplicity. The trade-offs are:

  • Operational complexity — running many services requires mature DevOps practices (monitoring, deployment pipelines, incident response)
  • Data consistency — eventual consistency across services means the platform must handle scenarios like "order confirmed but payment still processing"
  • Network latency — inter-service calls add overhead compared to in-process calls in a monolith

For most early-stage products, a modular monolith is a better starting point. Migrate to microservices when you have clear evidence that team velocity or scalability demands it. This architecture is what a mature, high-traffic e-commerce platform looks like — it's a target state, not a starting point.

//Trade-offs

Design Decisions

An architecture is a record of trade-offs. For every major choice here: what won, what lost, and why the constraints made it so.

01

Inter-service communication protocol

ChosegRPC between API Gateway and services
OverREST/JSON over HTTPGraphQL federation layer

At a 500:1 browse-to-checkout ratio, the gateway forwards a huge volume of internal calls on every page load. gRPC's binary framing and generated stubs cut serialization overhead and catch schema drift at compile time — a REST/JSON hop would add latency exactly where the search p95 budget is tightest. GraphQL federation solves a different problem (client-driven queries), not internal service-to-service fan-out.

02

Order database

ChoseSharded DynamoDB, partitioned by orderId
OverSingle PostgreSQL instanceManually sharded PostgreSQL

At 1M orders/day, the access pattern is almost entirely single-key writes and point lookups by orderId — the same access-pattern-first modeling DynamoDB's single-table design is built around. A single Postgres instance would need hand-rolled sharding to hit the same write throughput, without gaining any relational feature the order service actually uses.

03

Payment coordination

ChoseSynchronous charge call, then async event publication (hybrid orchestration + choreography)
OverPure choreography — Order Service only reacts to payment eventsA dedicated saga-orchestrator service

Order Service calls Payment Service directly so the shopper gets a definitive success/decline before checkout resolves — waiting on an event round-trip would leave the UI guessing. The Payment Event published afterward still lets Notification Service and future consumers react without coupling to the checkout request. A full saga orchestrator earns its keep once a third service (e.g. inventory reservation) needs compensating actions; with two services, the direct call is simpler and just as correct.

04

Catalog search

ChoseDedicated Elasticsearch index alongside Product DB
OverPostgreSQL full-text search (tsvector)A hosted search SaaS

50M SKUs with free-text queries, typo tolerance, and faceted filtering is exactly what Postgres tsvector starts to strain under past a few million rows. Elasticsearch is read-only and eventually consistent from Product DB's point of view — a stale index degrades relevance, never correctness, since the checkout stock check still hits Product DB directly.

05

Edge security

ChoseWAF applied between CloudFront and the ALB, in front of API Gateway
OverWAF only at the load balancerNo dedicated WAF — rely on API Gateway auth alone

Placing WAF behind the CDN but in front of the ALB filters payload-level attacks — SQL injection, catalog-scraping bots — before they reach any compute tier, while CloudFront still absorbs and caches the legitimate traffic. API Gateway auth alone only catches identity problems, not attacks against product-svc or order-svc themselves.

06

User read scaling

ChosePostgreSQL read replica for profile/session lookups
OverCache-only (Redis) with no replicaRoute all reads to the primary

Profile lookups happen on nearly every authenticated request, competing with the small but latency-sensitive stream of writes (registration, profile edits) on the same primary. A read replica absorbs that read volume without adding cache-invalidation complexity for data that changes rarely — Redis stays reserved for genuinely hot, short-lived session state.

//What Breaks First

Bottlenecks & Failure Modes

Reading a system means sensing where it cracks under 10× load. These are the pressure points of this design, and how it holds.

Risk 01

Flash-sale checkout stampede

Failure mode

A promotional event can spike checkout QPS far past the ~2K/s peak baseline in minutes, and every checkout makes a synchronous call into Payment Service — a downstream processor with its own rate limits.

Mitigation

API Gateway rate-limits checkout requests per user/IP, Order Service treats the charge call as idempotent via a per-order key so retries are safe, and DynamoDB's hash-partitioned writes spread the order-write burst evenly instead of hammering one shard.

Risk 02

Viral product page thundering herd

Failure mode

A single product going viral sends a disproportionate share of the 500:1 browse traffic at one SKU's cache key and search query, risking a stampede on Redis and Elasticsearch simultaneously on a cold cache.

Mitigation

Redis absorbs steady-state hot-key traffic, CloudFront caches category and product pages at the edge before they reach the gateway, and request coalescing at Product Service collapses concurrent cache misses into a single Elasticsearch query.

Risk 03

Payment Service on the critical path

Failure mode

Because Order Service calls Payment Service synchronously, a slow or unavailable payment processor stalls every checkout in flight, not just the ones actively charging.

Mitigation

Order Service wraps the charge call in a timeout and circuit breaker; orders that time out stay pending rather than failing outright, and a reconciliation job resolves them once Payment Service recovers instead of losing the order.

Risk 04

Event bus backlog during order surges

Failure mode

A sale-driven burst of Order and Payment events can outpace Notification Service's consumption rate, growing an unbounded backlog on the Event Bus.

Mitigation

Publishing is fire-and-forget and off the checkout path by design — a growing backlog delays confirmation emails, never checkout itself. Notification Service scales its consumer group horizontally, and Kafka's retention window buys time to drain it.

Risk 05

Overselling the last unit

Failure mode

Elasticsearch's view of stock levels can lag Product DB by seconds, and two concurrent checkouts can both see "in stock" for the last unit of a SKU.

Mitigation

Elasticsearch is used for search ranking only — the authoritative stock check happens as a conditional write against Product DB at charge time, so the second concurrent checkout fails the write and is told the item just sold out, rather than both succeeding.

//Break Things on Purpose

Failure Lab

Most of this architecture exists because any one box can die. Take a component offline and the diagram above shows the blast radius: what goes down, what degrades, and what the user feels.

API Gateway

API Gateway

The entire storefront goes dark — no requests reach any backend service, since every request funnels through this single front door.

Payment Service

REST API

Shoppers can still browse and add items to their cart, but checkout stalls — new orders stay stuck in pending instead of confirming.

Order DB (Sharded)

DynamoDB

No new orders can be created or updated at all — checkout fails outright. Browsing is unaffected since it doesn't touch this database.

Redis Cluster

Redis

Browsing and checkout both keep working, but every session lookup and product-cache read falls through to the database — page loads noticeably slow down under load.

Event Bus

Kafka

Checkout and browsing are completely unaffected — shoppers can still create and pay for orders. Confirmation emails and any other event-driven consumer simply stop until the bus recovers.

Elasticsearch

Elasticsearch

Full-text product search stops returning results, but checkout is completely unaffected — the authoritative stock and price check never depended on the search index.

//Active Recall

Test Yourself

If you can answer these without scrolling back up, the architecture is yours. Try each one out loud before revealing.

//Go Deeper

Further Reading

//A Word

From the Creator

Every great system starts as a sketch on a whiteboard. The ability to zoom out and see the whole picture (how services connect, where data flows, what breaks and why) is what separates engineers who build features from engineers who build systems. This diagram is more than boxes and arrows. It's a map of decisions, trade-offs, and intentional design.

Studying architectures isn't just about passing interviews. It's about training your intuition. The more systems you take apart, the better you get at sensing where a monolith will crack, where a queue belongs, or when a cache is hiding a deeper problem. You start seeing patterns instead of chaos.

So keep reading, keep tracing those edges, keep asking "why this way and not that way." The engineers who truly understand large systems are the ones who never stop being curious about how things fit together. That curiosity is the only ingredient that really matters.

Atharva Arbat@arbat_atharva

//Explore

More Patterns