Circuit Breaker Pattern


The Circuit Breaker pattern is a design pattern used to prevent cascading failures in distributed systems. It acts as a proxy for operations that might fail, monitoring recent failures and using that information to decide whether to allow the operation to proceed or simply return an error immediately without attempting the potentially failing operation.

Imagine a scenario where Service A depends on Service B. If Service B experiences an outage, Service A might continuously retry requests to Service B, exhausting its own resources and potentially contributing to the overall system instability. This is where the Circuit Breaker comes in.

The Circuit Breaker has three states:

  1. Closed: In this state, requests flow freely through the circuit breaker to Service B. The circuit breaker monitors failures.

  2. Open: After a certain number of failures within a specified time window, the circuit breaker trips and transitions to the Open state. In this state, requests are immediately rejected with an error, preventing further calls to the failing Service B.

  3. Half-Open: After a timeout period in the Open state, the circuit breaker transitions to the Half-Open state. In this state, a limited number of requests are allowed to pass through to Service B. If these requests succeed, the circuit breaker assumes Service B has recovered and transitions back to the Closed state. If any request fails, the circuit breaker transitions back to the Open state and resets the timeout period.

Here’s a simplified example implementation using JavaScript:

class CircuitBreaker {
  constructor(request, threshold, timeout) {
    this.request = request;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.lastFailureTime = 0;
  }

  async execute(...args) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime < this.timeout) {
        throw new Error('Circuit breaker open');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const response = await this.request(...args);
      this.reset();
      return response;
    } catch (error) {
      this.trackFailure();
      throw error; 
    }
  }

  trackFailure() {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
    }
  }

  reset() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }
}


// Example usage:
async function makeRequest(url) {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    return await response.json();
}

const breaker = new CircuitBreaker(makeRequest, 3, 5000); // 3 failures, 5 second timeout


async function test() {
    try {
        const data = await breaker.execute("https://example.com/api/data");
        console.log("Data:", data);
    } catch (error) {
        console.error("Error:", error.message);
    }

    try {
        const data = await breaker.execute("https://example.com/api/data");
        console.log("Data:", data);
    } catch (error) {
        console.error("Error:", error.message);
    }

}
test()



This example demonstrates the basic principles of the Circuit Breaker pattern. Real-world implementations often incorporate more sophisticated features such as metrics tracking and dynamic configuration of thresholds and timeouts. By using a circuit breaker, you can improve the resilience and stability of your distributed system by preventing cascading failures and allowing services to gracefully degrade under pressure.