Marcio Cunha

How to build resilient microservices with circuit breakers

Learn how circuit breakers and fallbacks work in synchronous microservice communication to prevent cascading failures in production environments.

Marcio Cunha•4 min
Also available in:PortuguêsEspañol
Summary
  • Distributed systems frequently fail due to high interdependency among synchronous components.
  • The circuit breaker pattern stops repeated calls to unstable services, protecting the system from overload.
  • Fallback strategies ensure alternative responses or graceful degradation when external dependencies go down.
  • Continuous monitoring of latency and error rates defines the ideal thresholds for state transitions.
  • Integrating resilience requires combining failure isolation with complementary strategies like request throttling .

The silent danger of cascading failures in distributed systems

When we split a monolithic application into multiple microservices, we gain agility and the ability to scale specific parts independently. However, we introduce a critical dependency: synchronous network communication. In practice, this means that if the payment service takes too long to respond, the main application keeps accumulating requests, hanging threads, and exhausting resources until the entire system collapses like a house of cards.

This chain reaction is any software engineer's nightmare in production environments. To combat this problem, we need to adopt architectural patterns focused on fault tolerance. Just like a household circuit breaker trips during an overload to prevent a fire, in software development we use the circuit breaker mechanism to cut off the flow of calls before chaos takes over.

How the circuit breaker works in practice

The circuit breaker operates by continuously monitoring the health of external calls made to another service. It essentially has three operational states: closed, open, and half-open. In practice, when the breaker is closed, requests pass through freely. If the error rate or timeout threshold exceeds a pre-configured limit, the breaker switches to the open state.

With an open circuit, new calls to the problematic service fail locally and immediately, without even trying to open a useless network connection. This saves precious CPU resources and prevents the dependent application from further overloading a server that is already on the verge of collapse. After a specified waiting period, the component enters a half-open state, allowing a single test request to pass through and verify if the external service has recovered.

Implementing fallback to ensure graceful degradation

Cutting off traffic with a circuit breaker prevents overload, but leaves the user interface or consuming API without a useful response. This is where the fallback concept comes in, acting as a strategic Plan B. In practice, the fallback is an alternative code path executed whenever the main call fails or when the circuit breaker is open.

If a product recommendation microservice fails, for example, the system can return a static list of top-selling items or display locally cached data instead of crashing the entire page for the user. This approach is known as graceful degradation: the user notices a minor loss of functionality, but the core service remains fully operational and stable.

To build robust APIs, resilience patterns must be paired with security practices, such as implementing effective rate limiting at the edge to mitigate abuse and unexpected traffic spikes.

Code example for structuring protection

To illustrate the practical implementation, imagine using a resilience library in an enterprise service. The snippet below demonstrates the basic configuration of a circuit breaker tied to a fallback function in a Node.js environment, protecting a critical HTTP call.

const CircuitBreaker = require('opossum');

async function callPaymentService() {
  // Synchronous communication logic with external service
  return fetch('https://internal-payments/api');
}

const options = {
  timeout: 3000, // If it takes longer than 3 seconds, fail
  errorThresholdPercentage: 50, // If 50% fail, open the circuit
  resetTimeout: 10000 // Try again after 10 seconds
};

const breaker = new CircuitBreaker(callPaymentService, options);

breaker.fallback(() => 'Service currently unavailable. Try again later.');

breaker.fire()
  .then(console.log)
  .catch(console.error);

This code encapsulates the network call so that any anomaly is handled immediately. Beyond safeguarding infrastructure with a circuit breaker, overall system stability benefits greatly from defense-in-depth measures like edge traffic controls.

Design decisions and operational trade-offs

Adopting fault tolerance patterns requires conscious architectural choices. The main trade-off lies in the added monitoring complexity and unpredictable behavior that can arise if wait times are misconfigured. If the timeout threshold is too short, you might trip the circuit prematurely due to normal network jitter. If it is too long, the system will keep hanging.

Another critical point is deciding what the fallback behavior should be. In read operations, returning cache or default data is relatively safe. However, in write operations or financial transactions, executing an incorrect fallback can cause data inconsistencies that are hard to trace. Therefore, engineering must align with business stakeholders on the acceptable impact when a secondary microservice becomes unreachable.

Conclusion and next steps for production

Building resilient microservices is not just about writing functional code, but preparing the application to fail gracefully when its surrounding environment collapses. The correct combination of circuit breakers, smart fallbacks, and constant monitoring transforms fragile systems into robust architectures capable of absorbing shocks without destroying the end-user experience.

As your infrastructure evolves, it pays off to expand these practices to other areas of software engineering, ensuring every component in your tech stack is built on the premise that network failure is an inevitable certainty.