How We Built an Atomic Circuit Breaker to Survive Third-Party API Outages at Scale
DEV Community

How We Built an Atomic Circuit Breaker to Survive Third-Party API Outages at Scale

Every modern backend relies on third-party APIs for things like payment gateways, SMS notifications, or shipping calculations. But what happens when that external API slows down or goes completely dark?

If your application keeps blindly hitting an offline API, your incoming request threads will hang waiting for a network timeout. Within minutes, your server's thread pool is completely exhausted, causing a cascading failure that brings your entire platform down just because one minor external service is having an outage.

To prevent this cascading failure, you need to implement a distributed Circuit Breaker Pattern.

The Strategy: Fail Fast Using Redis State Tracking

A Circuit Breaker acts exactly like an electrical circuit breaker in your home. It intercepts external API calls and monitors their failure rates. If the failure rate crosses a specific threshold, the circuit trips open, instantly blocking all subsequent calls to that API and returning a graceful fallback response without wasting any server resources.

Here is how to implement a clean, atomic Circuit Breaker using a Redis-backed abstraction:

namespace App\Services;

use Illuminate\Support\Facades\Redis;
use Exception;

class ResilientExternalProcessor
{
    private const CIRCUIT_KEY = 'circuit:external_api:state';
    private const FAILURE_COUNT_KEY = 'circuit:external_api:failures';
    private const FAILURE_THRESHOLD = 5; // Trip after 5 failures
    private const TIMEOUT_WINDOW = 60;   // Stay open for 60 seconds

    public function executeSecureCall(callable $apiCall)
    {
        // 1. Check if the circuit is currently "OPEN" (Tripped)
        if (Redis::get(self::CIRCUIT_KEY) === 'OPEN') {
            // Circuit is open; fail fast immediately to save server resources
            return $this->getFallbackData();
        }

        try {
            // 2. Attempt the actual network call
            $response = $apiCall();

            // Success! Reset failure tracking
            Redis::del(self::FAILURE_COUNT_KEY);

            return $response;
        } catch (Exception $e) {
            // 3. Handle failure and track it atomically
            $failures = Redis::incr(self::FAILURE_COUNT_KEY);

            if ($failures >= self::FAILURE_THRESHOLD) {
                // Trip the circuit to "OPEN" for 60 seconds
                Redis::setex(self::CIRCUIT_KEY, self::TIMEOUT_WINDOW, 'OPEN');
                Log::emergency("External API failure threshold reached. Circuit tripped OPEN.");
            }

            return $this->getFallbackData();
        }
    }

    private function getFallbackData(): array
    {
        // Return cached or localized data instead of throwing a 500 error
        return ['status' => 'fallback_mode', 'data' => []];
    }
}

Why This Protects Your Infrastructure

By wrapping fragile external network boundaries in this structure, an API outage at your payment or SMS provider will never cause your app to go down. Your server detects the failures, trips the circuit in memory, and handles traffic gracefully until the third-party service recovers.

Comments

No comments yet. Start the discussion.