Laravel Fuse

repository·main·Indexed 19 days ago

https://github.com/harris21/laravel-fuse

A circuit breaker implementation for Laravel queue jobs and external service calls. It protects workers from cascading failures by delaying jobs when service failure thresholds are exceeded. Features include the CircuitBreakerMiddleware, #[UseCircuitBreaker] attribute, a real-time monitoring status page, Artisan CLI controls, and customizable failure classifiers and recovery strategies. Requires PHP 8.3+ and Laravel 11+.

Tokens
7.3K
Snippets
25
Records
34
Agent score
63%

What's inside laravel-fuse

  1. Configure the Tracking Window

    main

    Failures are tracked in fixed-size time buckets (tumbling windows). The circuit only evaluates the failure rate once a window has gathered min_requests attempts. For low-volume services, you should increase the window size to ensure enough samples accumulate before the bucket rolls over.

    'reports' => [
        'min_requests' => 10,
        'window' => 600,   // 10 minutes
    ],
  2. Configure Peak Hours thresholds

    main

    You can define a peak_hours_threshold in your service configuration. During the specified peak_hours_start and peak_hours_end, Fuse will use this higher threshold to be more tolerant of failures, maximizing successful transactions during busy periods.

    'stripe' => [
        'threshold' => 40,              // Off-peak: more sensitive (40%)
        'peak_hours_threshold' => 60,   // Peak hours: more tolerant (60%)
        'peak_hours_start' => 9,        // 9 AM
        'peak_hours_end' => 17,         // 5 PM
    ],
  3. Configure and enable the Fuse Status Page

    main

    Fuse provides a real-time monitoring dashboard to view the state of all circuit breakers.

    1. Enable via .env: Set FUSE_STATUS_PAGE_ENABLED=true.
    2. Access: The page is available at /fuse by default, but you can change this using FUSE_STATUS_PAGE_PREFIX.
    3. Authorization: Access is protected by the viewFuse gate. You must define this gate in your AppServiceProvider to control who can view the dashboard.
    FUSE_STATUS_PAGE_ENABLED=true
    FUSE_STATUS_PAGE_PREFIX=fuse
    // app/Providers/AppServiceProvider.php
    use Illuminate\Support\Facades\Gate;
    
    Gate::define('viewFuse', function ($user = null) {
        return $user?->isAdmin();
    });
  4. System Requirements for Laravel Fuse

    main

    To use Laravel Fuse, ensure your environment meets the following requirements:

    • PHP: 8.3+
    • Laravel: 11+
    • Cache Driver: Redis is highly recommended for production environments. Using the file cache driver may lead to race conditions during recovery probing.
  5. Configure Fuse access via the 'viewFuse' Gate

    main

    Laravel Fuse automatically defines a viewFuse authorization gate if one does not already exist. By default, this gate returns true only when the application environment is set to local.

    If you want to restrict or expand who can access Fuse-related features (like dashboards or views), you should define your own viewFuse gate in your AuthServiceProvider to override this default behavior.

  6. Configure the Status Page settings

    main

    The status page behavior is controlled via the config/fuse.php file. Key options include:

    • enabled: Boolean to toggle the dashboard.
    • prefix: The URL path prefix (e.g., fuse).
    • middleware: An array of custom middleware to apply to the status page routes (replaces default middleware).
    • polling_interval: The frontend refresh interval in seconds (default is 2).
    // config/fuse.php
    
    'status_page' => [
        'enabled' => env('FUSE_STATUS_PAGE_ENABLED', false),
        'prefix' => env('FUSE_STATUS_PAGE_PREFIX', 'fuse'),
        'middleware' => [],          // Custom middleware (replaces default)
        'polling_interval' => 2,    // Frontend refresh interval in seconds
    ],
  7. Configure Fuse services

    main

    Service-specific settings are defined in config/fuse.php under the services key. This allows you to set different thresholds, timeouts, and window sizes for different external dependencies.

    // config/fuse.php
    
    return [
        'enabled' => env('FUSE_ENABLED', true),
    
        'default_threshold' => 50,      // Failure rate percentage to trip circuit
        'default_timeout' => 60,        // Seconds before testing recovery
        'default_min_requests' => 10,   // Minimum requests before evaluating
        'default_window' => 60,         // Seconds per failure-tracking window
    
        'services' => [
            'stripe' => [
                'threshold' => 50,
                'timeout' => 30,
                'min_requests' => 5,
                'release' => 15,
    
                // Peak hours: more tolerant during business hours
                'peak_hours_threshold' => 60,
                'peak_hours_start' => 9,   // 9 AM
                'peak_hours_end' => 17,    // 5 PM
            ],
            'mailgun' => [
                'threshold' => 60,
                'timeout' => 120,
                'min_requests' => 10,
                'window' => 300,        // 5-minute window
            ],
        ],
    
        'cache' => [
            'prefix' => env('FUSE_CACHE_PREFIX', 'fuse'),
        ],
    ];
  8. Protect jobs with CircuitBreakerMiddleware

    main

    To protect a queue job, add CircuitBreakerMiddleware to the job's middleware() method. You must provide a service name (e.g., 'stripe') and an optional release value (seconds to delay the job if the circuit is open).

    use Harris21//Fuse\Middleware\CircuitBreakerMiddleware;
    
    class ChargeCustomer implements ShouldQueue
    {
        public $tries = 0;           // Unlimited releases
        public $maxExceptions = 3;   // Only real failures count
    
        public function middleware(): array
        {
            return [new CircuitBreakerMiddleware('stripe', release: 20)];
        }
    
        public function handle(): void
        {
            // Your payment logic
            Stripe::charges()->create([...]);
        }
    }
  9. Implement a Custom Recovery Strategy

    main

    When a circuit is in the HALF-OPEN state, Fuse uses a probe to test recovery. You can override this behavior by implementing the RecoveryStrategy contract. This is useful if you want to gradually 'warm up' a service rather than returning to full traffic immediately upon the first success. Register the class via the recovery_strategy key in your service configuration.

    namespace App\Fuse;
    
    use Harris21\Fuse\CircuitBreaker;
    use Harris21\Fuse\Contracts\RecoveryStrategy;
    
    class CustomRecoveryStrategy implements RecoveryStrategy
    {
        public function allowsAttempt(CircuitBreaker $breaker): bool
        {
            // Logic to allow/deny probe
            return true;
        }
    
        public function recordSuccess(CircuitBreaker $breaker): bool
        {
            // Return true to close circuit, false to keep testing
            return true;
        }
    
        public function recordFailure(CircuitBreaker $breaker): void
        {
            // Logic for when a half-open job fails
        }
    }
    
    // Register in config/fuse.php
    'services' => [
        'stripe' => [
            'recovery_strategy' => \App\Fuse\CustomRecoveryStrategy::class,
        ],
    ],
  10. Listen to Circuit Breaker events

    main

    Fuse dispatches Laravel events whenever a circuit breaker changes state. You can create listeners to trigger alerts (e.g., Slack, PagerDuty) or log critical failures when a service goes down.

    Available event classes:

    • Harris21\Fuse\Events\CircuitBreakerOpened
    • Harris21\Fuse\Events\CircuitBreakerHalfOpen
    • Harris21\Fuse\Events\CircuitBreakerClosed
    use Harris21//Fuse\Events\CircuitBreakerOpened;
    use Illuminate\Support\Facades\Log;
    
    class AlertOnCircuitOpen
    {
        public function handle(CircuitBreakerOpened $event): void
        {
            Log::critical("Circuit breaker opened for {$event->service}", [
                'failure_rate' => $event->failureRate,
                'attempts' => $event->attempts,
                'failures' => $event->failures,
            ]);
        }
    }
  11. Use the CircuitBreaker class directly

    main

    You can use the CircuitBreaker class manually in your application logic (outside of jobs) to wrap external service calls. This allows you to implement fallback logic when a circuit is open.

    Common Methods:

    • $breaker->isOpen(): Returns true if the circuit is currently protecting the service.
    • $breaker->isClosed(): Returns true if the circuit is in normal operation.
    • $breaker->isHalfOpen(): Returns true if the circuit is testing recovery.
    • $breaker->recordSuccess(): Records a successful request.
    • $breaker->recordFailure($exception): Records a failed request with the provided exception.
    • $breaker->getStats(): Returns full statistics for the current window.
    • $breaker->reset(): Manually resets the circuit to CLOSED.
    use Harris21\Fuse\CircuitBreaker;
    
    $breaker = new CircuitBreaker('stripe');
    
    if (!$breaker->isOpen()) {
        try {
            $result = Stripe::charges()->create([...]);
            $breaker->recordSuccess();
            return $result;
        } catch (Exception $e) {
            $breaker->recordFailure($e);
            throw $e;
        }
    } else {
        // Circuit is open - use fallback
        return $this->fallbackResponse();
    }