
Microservices Patterns: API Gateway & Circuit Breakers
Abhay Vachhani
Developer
A monolith is simple to deploy but hard to scale. Microservices are easy to scale but hard to manage. When you split your application into dozens of small services, you introduce a new set of problems: network latency, distributed transactions, and cascading failures. To tame this chaos, you need specific design patterns that go beyond basic routing.
1. The API Gateway Pattern
Never let clients talk directly to your internal microservices. This exposes your internal infrastructure and creates security risks. Instead, use an **API Gateway** (like Kong, Nginx, or a custom Node.js gateway).
The Gateway acts as the single entry point. It handles:
- Routing: Directing
/usersto the User Service and/ordersto the Order Service. - Authentication: Verifying tokens once at the edge.
- Rate Limiting: Protecting internal services from traffic spikes.
2. Resilience: Circuit Breakers and Bulkheads
In a distributed system, failure is inevitable. If the "Payment Service" goes down, your "Checkout Service" shouldn't hang forever.
- Circuit Breaker: Wraps external calls. If calls start failing (e.g., 50% error rate), the circuit "opens" and fails immediately, giving the downstream service time to recover.
- Bulkhead Pattern: Isolates resources. Just like a ship has watertight compartments, your app should have separate connection pools for different services. If the "Inventory DB" is slow, it shouldn't exhaust the connections used for the "User DB".
3. The Saga Pattern: Distributed Transactions
You cannot use a database transaction across two different services. If the "Order Service" creates an order but the "Payment Service" fails to charge the card, how do you rollback? You use a **Saga**.
Choreography: Services listen for events. Order Service emits OrderCreated. Payment Service listens, tries to charge, and emits PaymentSuccess or PaymentFailed. If failed, Order Service listens and cancels the order.
Orchestration: A central "Orchestrator" service tells everyone what to do. It calls Order, then Payment, then Email. If one fails, it calls the compensating actions (Undo Order, Refund) in reverse order.
4. The Sidecar Pattern (Service Mesh)
As your grid grows, you don't want to implement logging, tracing, and TLS in every single microservice (Node, Go, Python). In the **Sidecar** pattern, you run a proxy (like Envoy or Istio) next to your main container. All network traffic goes through the sidecar, which handles the "cross-cutting concerns" transparently. This allows your developers to focus purely on business logic.
5. The Outbox Pattern: Dual-Write Consistency
When you save a user to the DB and sending a "Welcome" message to Kafka, what happens if the DB save succeeds but the Kafka publish fails? You have an inconsistency.
The Fix: Save the message to an "Outbox" table in the same transaction as your data. Then, have a separate background process read the Outbox table and publish to Kafka reliably. This guarantees At-Least-Once delivery.
Conclusion
Microservices require a shift in mindset from "preventing failure" to "managing failure." By implementing Sagas for consistency, Bulkheads for isolation, and Sidecars for observability, you build a system that degrades gracefully rather than crashing catastrophically.
FAQs
When should I switch to microservices?
Don't start with microservices. Stick to a modular monolith until your team size or scaling requirements (e.g., distinct CPU vs memory profiles for different features) force you to split.
What is the "Strangler Fig" pattern?
It's a strategy to migrate a legacy monolith by gradually replacing specific functionalities with new microservices, eventually strangling the old system entirely.