Microservices Design Patterns Explained with Examples
The 10 microservices design patterns you will actually use, from API gateway and database per service to saga, outbox, CQRS and circuit breaker, each with a worked e-commerce example.
Prerequisites
- •Basic understanding of REST APIs
- •Familiarity with databases and transactions
What You'll Learn
- Pick the right pattern for migration, client access, data, transactions, reads and failure isolation
- Model a multi-service order flow as a saga with compensating actions
- Publish events reliably with the transactional outbox
- Contain failures with timeouts, circuit breakers and bulkheads
Splitting an application into microservices solves some problems, like independent deploys, independent scaling, and teams that don't step on each other. It also creates new ones: how clients find the right service, how data stays consistent without a shared database, and how one slow service avoids taking down the rest. Microservices design patterns are the established answers to those problems. This guide covers the ten you'll actually use, grouped by the problem each one solves, with an example for every one built around the same e-commerce system: Orders, Payments, Inventory and Shipping services.
Patterns at a glance
1. Strangler fig (migration)
Problem: you have a working monolith and can't stop the world to rewrite it.
Pattern: put a routing layer in front of the monolith, build one capability as a new service, and route only that capability's traffic to it. Repeat until the monolith has nothing left, like a strangler fig growing around its host tree.
Example: route /api/shipping/* to the new Shipping service at the proxy; everything else still hits the monolith. If the new service misbehaves, flip the route back.
Watch out: the new service and the monolith often need the same data during the transition. Decide early which side owns it and sync the other way with events, not shared tables.
2. API gateway (client access)
Problem: a product page needs data from four services. Making the browser call all four means four round trips, four auth checks and clients coupled to your internal layout.
Pattern: a single entry point that routes requests, terminates TLS, authenticates, rate-limits and can aggregate several backend calls into one response. See API gateway.
Example: GET /orders/42 hits the gateway, which validates the JWT, calls Orders for the order and Shipping for the tracking status, and returns one JSON document.
Watch out: keep business logic out of the gateway, or it becomes a new monolith that every team has to change.
3. Backend for frontend (client access)
Problem: the mobile app wants small payloads, the web app wants rich ones, and a partner API wants stable versioned fields. One gateway serving all three ends up serving none well.
Pattern: one thin gateway per client type, owned by the team that builds that client. See backend for frontend.
Example: the mobile BFF returns an order summary with three fields; the web BFF returns the full order with line items and payment history.
4. Database per service (data ownership)
Problem: if Orders and Inventory share tables, a schema change in one breaks the other, and you can't deploy them independently. That's a distributed monolith.
Pattern: each service owns its data store, and other services get that data only through its API or its events. Services can even use different databases: Orders on PostgreSQL, the product catalogue on a document store, sessions on Redis.
Example: Shipping needs the delivery address. Instead of querying the Orders database, it stores its own copy from the OrderPlaced event.
Watch out: this is the pattern that makes the next three necessary. Without a shared database you lose cross-service joins and ACID transactions.
5. Saga (transactions across services)
Problem: placing an order must reserve stock, charge the card and create a shipment. There is no single database transaction that spans three services.
Pattern: a saga is a sequence of local transactions. Each step publishes an event or command that triggers the next; if a step fails, earlier steps are undone by compensating actions.
Example (choreography):
OrderPlaced.StockReserved.PaymentFailed.PaymentFailed by releasing the reservation; Orders marks the order CANCELLED.In choreography each service reacts to events, as above, which is simple for three or four steps. In orchestration a dedicated orchestrator (often a workflow engine such as Temporal or AWS Step Functions) tells each service what to do next and tracks state, which is easier to follow and debug for longer flows.
Watch out: compensations are business actions, not rollbacks. You can't un-send an email; you send a correction. Every step must also be idempotent, because messages get redelivered.
6. Transactional outbox (reliable events)
Problem: Orders saves the order and then publishes OrderPlaced to the broker. If the process crashes between the two, the order exists but nobody hears about it.
Pattern: in the same database transaction as the business change, insert the event into an outbox table. A separate relay process reads the outbox and publishes to the broker, marking rows as sent. Change data capture tools such as Debezium can do the relay by tailing the database log.
Example:
BEGIN;
INSERT INTO orders (id, status, total) VALUES ('42', 'PENDING', 59.00);
INSERT INTO outbox (aggregate_id, type, payload) VALUES ('42', 'OrderPlaced', '{"orderId":"42","total":59.00}');
COMMIT;Watch out: the relay can publish a message twice (it published, then crashed before marking it sent), so consumers must handle duplicates.
7. CQRS and API composition (reads across services)
Problem: the "My orders" page needs order status, payment status and tracking number, which live in three services.
API composition: the gateway or a composer service calls all three and joins the results in memory. Fine for simple views with low traffic.
CQRS (Command Query Responsibility Segregation): writes go to the owning services as usual, while a separate read model subscribes to their events and maintains a denormalised view built for that page. Reads hit one fast table.
Example: an order_history read store is updated by OrderPlaced, PaymentCaptured and ShipmentDispatched events and serves the page with one query.
Watch out: the read model is eventually consistent. Design the UI for it, for example by showing "payment processing" until the event arrives. CQRS is often paired with event sourcing, but it doesn't require it.
8. Circuit breaker (failure isolation)
Problem: Payments is slow. Orders keeps calling it, threads pile up waiting, and now Orders is down too. One failure cascades.
Pattern: wrap calls in a circuit breaker. After a threshold of failures it opens and fails fast without calling the service; after a cooldown it goes half-open and lets a few trial requests through; if they succeed it closes again.
Example: with a breaker around Payments, Orders returns "We've saved your order and will confirm payment shortly" instead of hanging, and retries later from a queue.
Watch out: always pair breakers with timeouts and a sensible fallback. A breaker without a fallback just turns slow errors into fast errors.
9. Bulkhead (failure isolation)
Problem: one noisy dependency uses up the shared connection pool, so calls to healthy dependencies starve.
Pattern: give each dependency its own limited pool of connections or threads, like watertight compartments in a ship. If one fills up, the others still work.
Example: Orders allows at most 20 concurrent calls to Payments and 20 to Inventory. A Payments outage can never use the slots Inventory needs.
10. Service discovery (finding services)
Problem: instances scale up and down and change IP addresses. Hard-coded hosts break.
Pattern: services register with a registry and clients look them up by name. In Kubernetes this is built in: a Service object gives a stable DNS name (payments.default.svc.cluster.local) in front of changing pods. Outside Kubernetes, tools like Consul fill the role.
How to choose
Start with the fewest patterns that solve the problem you actually have:

