webrek/laravel-circuit-breaker

A circuit breaker for Laravel: stop hammering a failing dependency, fail fast, and recover automatically.

Maintainers

Package info

github.com/webrek/laravel-circuit-breaker

pkg:composer/webrek/laravel-circuit-breaker

Statistics

Installs: 37

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-06-17 02:03 UTC

This package is auto-updated.

Last update: 2026-06-29 20:45:44 UTC


README

Última versión en Packagist Descargas totales Pruebas Versión de PHP Licencia

Evita que una dependencia con fallas arrastre a tu aplicación con ella. Envuelve las llamadas a un servicio externo en un circuit breaker: tras suficientes fallas se abre (trips open) y falla rápido —para que no se acumulen más solicitudes sobre un endpoint caído— y luego sondea para detectar la recuperación y se cierra a sí mismo cuando el servicio vuelve a estar sano.

Por qué

Cuando un servicio aguas abajo (una pasarela de pagos, una API de un socio, un endpoint de webhook) se vuelve lento o se cae, cada solicitud hacia él se queda esperando hasta agotar el tiempo de espera. Esas solicitudes se acumulan, agotan tus workers y el pool de conexiones, y su caída se convierte en tu caída: una falla en cascada. Un circuit breaker vigila las fallas y, una vez que cruzan un umbral (threshold), corta en seco las llamadas siguientes durante un cooldown para que tu aplicación siga respondiendo mientras la dependencia se recupera.

use Webrek\CircuitBreaker\Facades\CircuitBreaker;

$response = CircuitBreaker::for('payments')->call(
    fn () => Http::timeout(3)->post($url, $payload)->throw(),
    fallback: fn () => null,   // se devuelve mientras el circuito está abierto
);

Instalación

composer require webrek/laravel-circuit-breaker

Opcionalmente publica la configuración:

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

El estado se guarda en el cache. En producción apúntalo a un almacén centralizado (Redis) para que el breaker se comparta entre todos los procesos y servidores: los drivers de arreglo y archivo solo protegen a un único proceso.

Cómo funciona

Un circuito se mueve entre tres estados:

  • Closed (cerrado) — las llamadas pasan. Cada falla consecutiva se cuenta; una vez que alcanza failure_threshold, el circuito se abre (trips open).
  • Open (abierto) — las llamadas se cortan de inmediato (fallback o CircuitOpenException) sin tocar la dependencia. Tras cooldown_seconds, la siguiente llamada se permite a modo de prueba: half-open (semiabierto).
  • Half-open (semiabierto) — se dejan pasar llamadas de prueba. success_threshold éxitos seguidos cierran el circuito; una sola falla lo vuelve a abrir.

Uso

Con un fallback

Cuando se proporciona un fallback, un circuito abierto (o una llamada que falla) lo devuelve en lugar de lanzar una excepción. El fallback recibe el Throwable:

$rate = CircuitBreaker::for('fx-api')->call(
    fn () => $this->fetchLiveRate(),
    fallback: fn (\Throwable $e) => $this->lastKnownRate(),
);

Sin un fallback

Omítelo y el breaker relanza la excepción subyacente —o lanza CircuitOpenException mientras está abierto— para que la manejes tú mismo:

use Webrek\CircuitBreaker\Exceptions\CircuitOpenException;

try {
    CircuitBreaker::for('payments')->call(fn () => $gateway->charge($order));
} catch (CircuitOpenException $e) {
    return back()->withErrors('Payments are temporarily unavailable.');
}

No toda excepción es una falla

Un 422 por un error de validación significa que tu solicitud estaba mal, no que el servicio esté caído: no debería abrir el breaker. Enumera esas excepciones bajo ignore y pasarán de largo sin afectar al circuito:

// config/circuit-breaker.php
'defaults' => [
    'ignore' => [
        Illuminate\Http\Client\RequestException::class, // solo si tratas los 4xx como error del llamador
    ],
],

Se combina con el outbox

El relay de webrek/laravel-outbox puede entregar a través de un breaker para que deje de reintentar contra un endpoint que ya está caído, y reanude automáticamente una vez que se recupera.

Observabilidad y operación

Los eventos del ciclo de vida te permiten alertar ante cambios de estado:

Evento Cuándo
CircuitOpened Un circuito se abrió.
CircuitHalfOpened Un circuito abierto inició una prueba de recuperación.
CircuitClosed Un circuito se recuperó.

Fuerza el cierre de un circuito a mano:

php artisan circuit-breaker:reset payments

Inspecciona el estado en código con CircuitBreaker::for('payments')->state() y ->available().

Configuración

return [
    'cache' => [
        'store' => env('CIRCUIT_BREAKER_CACHE'),   // null = por defecto; usa Redis en producción
        'prefix' => 'circuit-breaker',
        'ttl' => 86400,
    ],
    'defaults' => [
        'failure_threshold' => 5,    // fallos consecutivos que abren el circuito
        'cooldown_seconds' => 30,    // open → half-open después de esto
        'success_threshold' => 1,    // éxitos de prueba necesarios para cerrar
        'ignore' => [],              // excepciones que no cuentan como fallos
    ],
    'circuits' => [
        'payments' => ['failure_threshold' => 3, 'cooldown_seconds' => 60],
    ],
];

Requisitos

Componente Versión
PHP 8.2+
Laravel 12.x / 13.x
Cache Un almacén compartido (Redis) en producción

Pruebas

composer install
composer test

Contribuir

Consulta CONTRIBUTING.md.

Seguridad

Por favor revisa la política de seguridad antes de reportar una vulnerabilidad.

Licencia

Publicado bajo la licencia MIT.