Circuit Breaker Pattern

What is Circuit Breaker?

The Circuit Breaker Pattern prevents cascading failures by stopping requests to a failing service and providing fallback responses.

States

┌─────────┐
│ CLOSED  │ ──[failures > threshold]──> ┌──────┐
│(Normal) │                              │ OPEN │
└─────────┘ <──[timeout expires]──────── └──────┘
     ▲                                       │
     │                                       │
     │         ┌──────────────┐             │
     └─────────│ HALF_OPEN    │<────────────┘
               │(Testing)     │
               └──────────────┘

Basic Implementation

class CircuitBreaker {
  constructor(fn, options = {}) {
    this.fn = fn;
    this.state = 'CLOSED';
    this.failureCount = 0;
    this.successCount = 0;
    this.nextAttempt = Date.now();
    
    this.failureThreshold = options.failureThreshold || 5;
    this.successThreshold = options.successThreshold || 2;
    this.timeout = options.timeout || 60000;
  }
  
  async execute(...args) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      
      this.state = 'HALF_OPEN';
    }
    
    try {
      const result = await this.fn(...args);
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
  
  onSuccess() {
    this.failureCount = 0;
    
    if (this.state === 'HALF_OPEN') {
      this.successCount++;
      
      if (this.successCount >= this.successThreshold) {
        this.state = 'CLOSED';
        this.successCount = 0;
      }
    }
  }
  
  onFailure() {
    this.failureCount++;
    this.successCount = 0;
    
    if (this.failureCount >= this.failureThreshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
    }
  }
  
  getState() {
    return this.state;
  }
}

Usage Example

// Wrap service call
async function callUserService(userId) {
  const response = await axios.get(`http://user-service/users/${userId}`);
  return response.data;
}

const breaker = new CircuitBreaker(callUserService, {
  failureThreshold: 3,
  successThreshold: 2,
  timeout: 30000
});

// Use circuit breaker
try {
  const user = await breaker.execute('user_123');
  console.log(user);
} catch (error) {
  console.error('Service unavailable');
}

With Fallback

class CircuitBreakerWithFallback extends CircuitBreaker {
  constructor(fn, fallback, options) {
    super(fn, options);
    this.fallback = fallback;
  }
  
  async execute(...args) {
    try {
      return await super.execute(...args);
    } catch (error) {
      if (this.state === 'OPEN') {
        return await this.fallback(...args);
      }
      throw error;
    }
  }
}

// Usage
const breaker = new CircuitBreakerWithFallback(
  callUserService,
  async (userId) => ({ id: userId, name: 'Guest User' }),
  { failureThreshold: 3 }
);

const user = await breaker.execute('user_123');

Using Opossum Library

const CircuitBreaker = require('opossum');

// Create circuit breaker
const breaker = new CircuitBreaker(callUserService, {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
});

// Fallback
breaker.fallback(() => ({ id: null, name: 'Guest' }));

// Events
breaker.on('open', () => {
  console.log('Circuit breaker opened');
});

breaker.on('halfOpen', () => {
  console.log('Circuit breaker half-open');
});

breaker.on('close', () => {
  console.log('Circuit breaker closed');
});

// Execute
const user = await breaker.fire('user_123');

Monitoring

class MonitoredCircuitBreaker extends CircuitBreaker {
  constructor(name, fn, options) {
    super(fn, options);
    this.name = name;
    this.metrics = {
      requests: 0,
      successes: 0,
      failures: 0,
      rejections: 0
    };
  }
  
  async execute(...args) {
    this.metrics.requests++;
    
    if (this.state === 'OPEN') {
      this.metrics.rejections++;
      throw new Error('Circuit breaker is OPEN');
    }
    
    try {
      const result = await super.execute(...args);
      this.metrics.successes++;
      return result;
    } catch (error) {
      this.metrics.failures++;
      throw error;
    }
  }
  
  getMetrics() {
    return {
      name: this.name,
      state: this.state,
      ...this.metrics,
      successRate: this.metrics.successes / this.metrics.requests,
      failureRate: this.metrics.failures / this.metrics.requests
    };
  }
}

Multiple Services

class CircuitBreakerRegistry {
  constructor() {
    this.breakers = new Map();
  }
  
  register(name, fn, options) {
    const breaker = new CircuitBreaker(fn, options);
    this.breakers.set(name, breaker);
    return breaker;
  }
  
  get(name) {
    return this.breakers.get(name);
  }
  
  getAll() {
    return Array.from(this.breakers.entries()).map(([name, breaker]) => ({
      name,
      state: breaker.getState()
    }));
  }
}

// Setup
const registry = new CircuitBreakerRegistry();

registry.register('user-service', callUserService, {
  failureThreshold: 3,
  timeout: 30000
});

registry.register('order-service', callOrderService, {
  failureThreshold: 5,
  timeout: 60000
});

// Use
const userBreaker = registry.get('user-service');
const user = await userBreaker.execute('user_123');

Health Check Endpoint

app.get('/health/circuit-breakers', (req, res) => {
  const breakers = registry.getAll();
  
  const allClosed = breakers.every(b => b.state === 'CLOSED');
  
  res.status(allClosed ? 200 : 503).json({
    status: allClosed ? 'healthy' : 'degraded',
    breakers
  });
});

Benefits

  1. Prevents Cascading Failures: Stop calling failing services
  2. Fast Failure: Fail immediately when circuit is open
  3. Automatic Recovery: Test service health periodically
  4. Resource Protection: Prevent resource exhaustion
  5. Better User Experience: Provide fallback responses

Best Practices

  1. Set appropriate thresholds
  2. Implement fallbacks
  3. Monitor circuit state
  4. Log state changes
  5. Use different timeouts per service
  6. Test failure scenarios

Interview Tips

  • Explain pattern: Prevent cascading failures
  • Show states: Closed, Open, Half-Open
  • Demonstrate implementation: Basic circuit breaker
  • Discuss fallbacks: Graceful degradation
  • Mention monitoring: Track metrics
  • Show benefits: Fast failure, recovery

Summary

Circuit Breaker Pattern prevents cascading failures by monitoring service calls and stopping requests to failing services. Uses three states: Closed (normal), Open (failing), and Half-Open (testing recovery). Implement with failure thresholds and timeouts. Provide fallback responses for better user experience. Essential for resilient microservices.

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Microservices Knowledge

Ready to put your skills to the test? Take our interactive Microservices quiz and get instant feedback on your answers.