Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Circuit Breakers and Resilience Patterns

By Kokil Thapa | Last reviewed: August 2026

When a third-party payment gateway or SMS provider starts timing out, your entire application can grind to a halt as threads pile up waiting for responses that never come. Implementing circuit breakers and resilience patterns is the definitive way to isolate these failures and protect your core business logic from cascading collapse. In my experience building legal-tech portals and eCommerce systems where external integrations are critical, treating resilience as an architectural requirement rather than an afterthought is what separates stable production systems from fragile ones.

What Are Circuit Breakers and Resilience Patterns in Web Development?

Circuit breakers function exactly like electrical fuses: they monitor the flow of requests to a dependency and "trip" open when failures exceed a defined threshold, stopping all traffic to that dependency for a configured duration. This prevents your web server from exhausting its worker pool on hung connections. For developers working on Laravel API best practices, understanding this concept is mandatory because modern applications rarely exist in isolation; they depend on payment gateways like eSewa or Khalti, SMS providers, shipping APIs, and microservices.

The pattern consists of three distinct states that govern request flow:

  • Closed: Normal operation. Requests pass through to the service. Failures are counted against a threshold.
  • Open: The breaker has tripped. All requests fail immediately without attempting to contact the remote service. This returns a cached response or a graceful fallback.
  • Half-Open: After a timeout period, the breaker allows a single test request through. If it succeeds, the circuit closes; if it fails, the timer resets.
Circuit Breaker State MachineCLOSEDNormal FlowFailures < ThresholdOPENFail FastNo Remote CallsHALF-OPENTest RequestSingle Probe CallThreshold HitTimeout EndsProbe Succeeds → Reset CounterProbe Fails → Reset Timer
State transitions for circuit breakers and resilience patterns: how the system moves between normal operation, failure isolation, and recovery testing

Beyond the circuit breaker itself, resilience encompasses several complementary patterns. The Retry with Backoff pattern handles transient network glitches by retrying failed requests with exponentially increasing delays. The Bulkhead pattern isolates resource pools so a failure in one component (like an email queue) cannot starve another (like user authentication). The Timeout pattern ensures no single call can block indefinitely. Together, these form a defense-in-depth strategy essential for any production system integrating with Nepal's often-unreliable external infrastructure.

How Do You Implement a Circuit Breaker in Laravel 12?

Laravel 12 does not include a native circuit breaker in the framework core, but the ecosystem provides battle-tested solutions. My preferred approach for most client projects uses the spatie/laravel-circuit-breaker package because it integrates cleanly with Laravel's cache and configuration systems without requiring Redis-specific features. For teams already committed to Redis, redis-circuit-breaker offers atomic operations suitable for multi-server deployments.

Installation and Basic Configuration

composer require spatie/laravel-circuit-breaker

Publish the configuration file to customize thresholds per service:

php artisan vendor:publish --tag="circuit-breaker-config"

In config/circuit-breaker.php, define separate circuits for each external dependency. A common mistake I see on real client projects is using a single global circuit for all HTTP calls. This means if the SMS gateway fails, your payment processing also gets blocked. Always isolate circuits by service:

<?php
// config/circuit-breaker.php

return [
    'circuits' => [
        'esewa_payment' => [
            'threshold' => 5,           // Open after 5 failures
            'timeout' => 60,            // Stay open for 60 seconds
            'half_open_max_calls' => 1, // Test with single call
            'store' => 'redis',         // Use Redis for shared state
        ],
        'sms_gateway' => [
            'threshold' => 10,
            'timeout' => 120,
            'half_open_max_calls' => 1,
            'store' => 'file',          // File store OK for single-server
        ],
    ],
];

Wrapping Service Calls

Apply the circuit breaker at the service layer, not in controllers. This keeps resilience logic encapsulated and testable. When building REST APIs in Laravel, this separation becomes critical because the same service may be called from HTTP endpoints, queued jobs, and CLI commands:

<?php

namespace App\Services\Payment;

use Spatie\CircuitBreaker\Facades\CircuitBreaker;
use App\Exceptions\PaymentServiceUnavailableException;

class EsewaPaymentService
{
    public function processPayment(float $amount, string $reference): array
    {
        return CircuitBreaker::execute('esewa_payment', function () use ($amount, $reference) {
            $response = Http::timeout(10)
                ->withHeaders(['Authorization' => config('services.esewa.key')])
                ->post('https://api.esewa.com.np/v2/payments', [
                    'amount' => $amount,
                    'reference_id' => $reference,
                ]);

            if ($response->failed()) {
                throw new \RuntimeException('eSewa payment failed');
            }

            return $response->json();
        }, function () use ($reference) {
            // Fallback: log for manual reconciliation
            logger()->warning('eSewa circuit open', ['ref' => $reference]);
            
            throw new PaymentServiceUnavailableException(
                'Payment processing temporarily unavailable. Please try again later.'
            );
        });
    }
}

The fallback closure is where many implementations fail. Never return null silently from a fallback unless the caller explicitly handles it. Either return a meaningful default value, serve cached data, or throw a domain-specific exception that the UI can translate into a helpful message for users. On legal-tech portals I've built, we typically queue failed operations for retry while informing the user their request was received but is pending confirmation.

When Should You Use Retry Logic vs Circuit Breakers?

A frequent question from developers new to resilience engineering is whether to use retries, circuit breakers, or both. The answer depends entirely on the failure mode you're addressing. Understanding this distinction prevents misapplication that can make systems less reliable rather than more.

PatternBest ForFailure TypeRisk If Misused
Retry + BackoffTransient network errors, temporary rate limitsShort-lived, self-resolvingAmplifies load during outages
Circuit BreakerSustained outages, dependency failuresProlonged, requires interventionBlocks valid requests too early
CombinedProduction external integrationsMixed transient + sustainedComplexity increases debugging difficulty
Timeout OnlyNon-critical background tasksSlow responses acceptableWastes resources on doomed calls

The correct combination for most payment and notification services in Nepal is retry-first with circuit-breaker-as-backstop. Attempt 2-3 retries with exponential backoff for transient issues. If those retries exhaust without success, the circuit breaker counts it as one failure event. This prevents a single slow outage from triggering the breaker prematurely while still protecting against sustained downtime.

Retry + Circuit Breaker Decision FlowIncoming RequestCircuit OPEN?YESFallbackNOExecute + Retry LoopOn FailureRetries Left?YES: Wait + RetryNO: Record FailureReturn Success / Error
Combined retry and circuit breaker workflow: check circuit state first, then execute with bounded retries before recording failures

In practice, configure Laravel's built-in retry helper alongside your circuit breaker. The key insight is that the circuit breaker wraps the entire retry sequence, not individual attempts:

CircuitBreaker::execute('sms_gateway', function () {
    return retry(
        times: 3,
        callback: fn() => SmsGateway::send($message),
        sleepMilliseconds: 1000,
        when: fn(\Exception $e) => $e instanceof TransientNetworkException,
    );
}, fallback: fn() => QueueFailedSms::dispatch($message));

How Does the Bulkhead Pattern Prevent Resource Exhaustion?

Circuit breakers protect against bad responses, but they don't prevent resource starvation. If your application shares a single connection pool or thread pool across all services, a slow-but-not-failing dependency can consume every available slot, blocking healthy services. The bulkhead pattern solves this by partitioning resources.

For Laravel applications running on PHP-FPM, bulkheads manifest as separate queue workers and connection limits rather than thread pools. On a recent eCommerce project handling high-volume order processing, we separated payment verification, inventory updates, and notification sending into dedicated queues with independent worker counts. This ensured that a slowdown in the notification provider couldn't prevent orders from being confirmed.

# .env configuration for bulkhead isolation
QUEUE_CONNECTION=redis

# Dedicated queues with separate supervisor processes
PAYMENT_QUEUE_CONNECTION=redis-payment
INVENTORY_QUEUE_CONNECTION=redis-inventory  
NOTIFICATION_QUEUE_CONNECTION=redis-notification

Configure Supervisor to allocate workers proportionally to business priority. Payment verification gets more workers than notifications because payment failures directly impact revenue. This is operational resilience that no amount of application-level circuit breaking can replace.

What Monitoring Is Required for Circuit Breakers in Production?

Deploying circuit breakers without observability is dangerous. You need to know when circuits trip, how long they stay open, and whether fallbacks are being triggered frequently enough to indicate a systemic problem. Without metrics, you're flying blind and may discover issues only when customers complain.

Instrument your circuit breakers to emit events to your logging or monitoring stack. Spatie's package fires events you can listen to:

// app/Providers/EventServiceProvider.php
protected $listen = [
    \Spatie\CircuitBreaker\Events\CircuitOpened::class => [
        \App\Listeners\LogCircuitOpened::class,
    ],
    \Spatie\CircuitBreaker\Events\CircuitClosed::class => [
        \App\Listeners\LogCircuitRecovered::class,
    ],
];

For teams using Laravel Telescope or Sentry, integrate circuit state into your existing dashboards. Track these key metrics:

  • Trip frequency: How often does each circuit open? Frequent trips indicate chronic instability.
  • Recovery time: How long do circuits stay open? Longer durations suggest the timeout may be too conservative or the upstream issue persists.
  • Fallback invocation rate: High fallback usage degrades user experience even if the system stays up.
  • False positive rate: Are circuits tripping during normal variance? Your threshold may be too aggressive.

Set alerts on trip events, not just on complete outages. A circuit tripping is a leading indicator; waiting for total failure means you've already lost the benefit of the pattern. For Nepal-based teams managing infrastructure remotely, proactive alerting via SMS or Slack is essential since you may not have real-time dashboard access during power or connectivity interruptions.

Circuit Breaker Monitoring DashboardTrip Rate (Last Hour)12eSewa Payment CircuitAvg Recovery Time47sWithin Expected RangeFallback Usage8.3%SMS Gateway (24h)Circuit State Timeline (Last 6 Hours)ClosedOpenClosedOpenClosed00:0003:0006:00
Essential monitoring metrics for circuit breakers and resilience patterns: trip frequency, recovery duration, fallback rates, and state timeline visualization

Common Mistakes When Implementing Resilience Patterns

After years of debugging resilience implementations in production, certain anti-patterns recur consistently. Avoiding these saves significant troubleshooting time:

Setting thresholds too low. A threshold of 2-3 failures will trip during normal network jitter. Start with 5-10 failures within a rolling window and adjust based on observed behavior. Legal-tech applications processing government API calls often need higher thresholds due to inherent upstream instability.

Ignoring idempotency. Retrying non-idempotent operations causes duplicate payments, double-sent emails, or corrupted data. Before adding retry logic to any payment integration, verify the endpoint supports idempotency keys or design your own deduplication layer.

Shared cache stores across environments. Using the same Redis instance for development, staging, and production circuit state means a test-induced trip in staging blocks production traffic. Namespace your circuit breaker cache keys by environment.

No fallback degradation path. Returning generic errors when circuits open wastes the pattern's purpose. Cache read-only data, queue write operations for later, or offer partial functionality. Users prefer "your order is confirmed, tracking will update shortly" over "service unavailable."

Testing only the happy path. Write integration tests that simulate circuit-open conditions. Verify fallbacks execute correctly and that the system recovers when the upstream service returns. Chaos engineering principles apply even at the application level.

Building Resilient Systems That Survive Real-World Conditions

Circuit breakers and resilience patterns are not optional luxuries for high-scale systems; they are fundamental requirements for any application depending on external services. In Nepal's infrastructure context, where power fluctuations, ISP instability, and third-party API reliability vary significantly, these patterns directly determine whether your application serves users or generates support tickets.

Start small: add circuit breakers to your most critical external integrations first. Instrument them properly from day one. Tune thresholds based on real traffic, not theoretical models. Combine with retries, bulkheads, and timeouts for layered defense. Most importantly, treat resilience as a continuous practice, not a one-time implementation. Review your circuit breaker metrics weekly, adjust configurations as dependencies change, and document your fallback behaviors so the next developer understands the trade-offs you made.

If you're building systems where uptime matters and need practical guidance on implementing these patterns in your specific stack, reach out to discuss your architecture. Whether you're integrating local payment gateways, building legal-tech platforms, or scaling eCommerce operations, getting resilience right from the start prevents costly rewrites and customer trust erosion down the line.

Frequently Asked Questions

A circuit breaker prevents cascading failures by stopping requests to an unresponsive service after consecutive errors, allowing recovery time before retrying.

Implement when calling external APIs, payment gateways like eSewa or Khalti, or microservices where timeouts could block user requests and degrade overall system performance.

Retries repeat failed requests immediately; circuit breakers stop sending requests entirely after threshold breaches, preventing resource exhaustion during prolonged outages.

In my experience working on production Laravel applications, spatie/laravel-circuit-breaker and php-circuit-breaker are reliable choices compatible with PHP 8.2+. Spatie's package integrates cleanly with Laravel's service container and supports configurable thresholds, timeouts, and fallback callbacks without heavy dependencies. For Symfony 7.x projects, the circuit-breaker component in symfony/http-client provides native support. Always verify package compatibility with your exact PHP version before installation, as some older forks lack PHP 8.3+ support.

Start with five consecutive failures within sixty seconds to open the circuit, thirty-second cooldown before half-open state, and three successful requests in half-open to close. These values work well for Nepal payment gateway integrations where network latency varies. Adjust based on your specific service SLAs and traffic patterns. Monitor production metrics using Laravel Debugbar or logging to refine thresholds. Overly aggressive settings cause false positives during normal latency spikes, while lenient settings fail to protect downstream services during genuine outages.

Wrap external API calls inside queued jobs with circuit breaker checks before execution. If the circuit is open, fail the job gracefully using release() with backoff rather than throwing exceptions that trigger immediate retries. This prevents queue workers from hammering unavailable services. On projects integrating ConnectIPS or IME Pay webhooks, I configure separate circuit breakers per payment provider since their failure modes differ. Store circuit state in Redis for persistence across worker processes, ensuring all queue instances share consistent breaker status during high-throughput batch processing scenarios.

Yes, indirectly. When third-party scripts like analytics, chat widgets, or review platforms hang, they block rendering and increase Largest Contentful Paint. A client-side circuit breaker in JavaScript can skip loading non-critical resources after timeout, preserving page speed. Server-side, circuit breakers prevent slow API responses from delaying HTML generation. On legal-tech portals I have built, wrapping schema.org data fetching in circuit breakers ensured pages still rendered valid markup even when enrichment services failed. This maintains crawlability and prevents Googlebot from encountering timeout errors during indexing.

Serve cached responses, default values, or degraded functionality rather than error pages. For eCommerce product listings, display cached inventory counts instead of real-time stock checks. On booking systems like Adventure Third Pole Trek, show "contact us for availability" when supplier APIs fail instead of blocking the entire form. Log open-circuit events for monitoring but never expose technical errors to users. Fallbacks must be tested regularly; stale cache served indefinitely creates worse business outcomes than transparent downtime. Set maximum fallback duration and alert operations teams when exceeded.

Use mock HTTP clients in PHPUnit tests to simulate failures and verify state transitions. Laravel's Http::fake() lets you return sequential error responses to trigger opening, then success responses to test closing. Create dedicated integration tests that assert fallback content renders correctly. In staging environments, use tools like WireMock or Toxiproxy to inject latency and failures realistically. Never test against live payment gateways; sandbox credentials behave differently under load. Document expected behaviors so QA teams understand that temporary service unavailability is intentional resilience, not bugs requiring fixes.

Setting thresholds too high means circuits never open during real incidents. Sharing one global breaker across unrelated services causes unnecessary cascading blocks. Not resetting state after deployments leaves circuits permanently open in new releases. Missing health check endpoints prevents automatic recovery detection. On one production deployment, a team forgot to configure Redis persistence, causing circuit state loss on every restart and repeated failure storms. Always validate configuration matches actual service characteristics and monitor breaker metrics alongside application logs to catch misconfigurations before users experience degraded service.

Rate limiting controls request volume proactively; circuit breakers react to failures retroactively. They complement each other. Apply rate limits first to prevent overwhelming services, then use circuit breakers as safety nets when rate-limited services still fail due to internal issues. In Laravel middleware stacks, place rate limiters before circuit breaker checks. For Nepal payment integrations during Dashain peak seasons, I layer both: throttle to gateway-specified limits, then break if error rates exceed thresholds despite compliant request volumes. This dual approach handles both predictable traffic spikes and unexpected provider outages effectively.

Generally no. Database connections already have connection pooling, query timeouts, and retry mechanisms built into PDO and Eloquent. Adding circuit breakers adds complexity without meaningful benefit since databases rarely exhibit the partial-failure modes circuit breakers address. Exceptions exist for read replicas with known instability or external data warehouse connections. Instead, focus on proper indexing, query optimization, and connection pool sizing. If database slowness causes cascading failures, the root cause is usually missing indexes or N+1 queries, not lack of circuit protection. Fix those first before considering resilience patterns at the database layer.

Expose breaker state via dedicated health endpoints returning JSON with current status, failure counts, and last transition timestamps. Integrate with Prometheus or Grafana using laravel-metrics packages to track open/close/half-open transitions over time. Set alerts on sustained open states exceeding fallback duration limits. Log state changes with context including triggering service and error types for post-incident analysis. On shared EC2 infrastructure running multiple sister sites, centralized logging helps distinguish between isolated service failures and platform-wide issues. Monitoring transforms circuit breakers from silent safeguards into observable system health indicators.

Never expose internal service names, error details, or circuit state in user-facing responses. Attackers can probe endpoints to map dependencies and identify weak links. Sanitize logged errors to avoid leaking credentials or tokens captured during failed requests. Ensure fallback responses do not inadvertently serve sensitive cached data to unauthorized users. Validate that circuit breaker bypass cannot be triggered maliciously through crafted error responses. On legal-tech portals handling document uploads, I ensure circuit breakers on virus scanning services fail closed, rejecting uploads rather than accepting unscanned files when scanners are unavailable. Security trumps availability for sensitive operations.

Development adds eight to sixteen hours for basic implementation and testing, roughly NPR 40,000 to 80,000 (USD 300 to 600) at typical Nepal senior developer rates. Ongoing costs include Redis hosting (~NPR 1,500/month, ~USD 11) for state storage and monitoring setup time. For small businesses, weigh this against revenue loss from downtime. Payment gateway integrations justify the investment quickly; brochure sites may not. Start with simpler timeout and retry configurations, adding full circuit breakers only when incident history demonstrates need. Avoid over-engineering resilience for services with excellent uptime records and minimal business impact when unavailable.

Share this article

Quick Contact Options
Choose how you want to connect me: