webrek/laravel-circuit-breaker
Installation:
composer require webrek/laravel-circuit-breaker
Publish the config file:
php artisan vendor:publish --provider="Webrek\CircuitBreaker\CircuitBreakerServiceProvider" --tag="config"
First Use Case: Wrap an external API call in a circuit breaker. Example:
use Webrek\CircuitBreaker\Facades\CircuitBreaker;
try {
$response = CircuitBreaker::attempt('payment-gateway', function () {
return Http::post('https://api.gateway.com/pay', $data);
});
} catch (\Webrek\CircuitBreaker\Exceptions\CircuitBreakerException $e) {
// Handle circuit breaker failure (e.g., return cached data or fallback UI)
return response()->json(['error' => 'Service unavailable'], 503);
}
Configuration:
Define breakers in config/circuit-breaker.php:
'breakers' => [
'payment-gateway' => [
'timeout' => 5, // seconds
'max_failures' => 3,
'reset_timeout' => 30, // seconds
],
],
Idempotent Operations: Use the breaker for stateless or idempotent calls (e.g., API requests, webhooks). Avoid stateful operations (e.g., database transactions) inside the breaker.
Fallback Strategies:
$cachedData = Cache::get('payment_fallback');
if (!$cachedData) {
throw new \Webrek\CircuitBreaker\Exceptions\CircuitBreakerException('Fallback data not available');
}
Retry Logic:
Combine with Laravel’s retry helper for transient failures:
$response = retry(3, function () use ($data) {
return CircuitBreaker::attempt('payment-gateway', fn() => Http::post('...', $data));
}, 100); // 100ms delay between retries
Monitoring: Log breaker states and failures for observability:
CircuitBreaker::attempt('payment-gateway', function () {
// ...
}, function ($state) {
Log::info("Circuit breaker state: {$state->state}", ['failures' => $state->failures]);
});
CircuitBreaker::attempt('external-webhook', function () {
$job->dispatch($data);
});
public function handle($request, Closure $next) {
return CircuitBreaker::attempt('third-party-api', $next);
}
$breaker = app(\Webrek\CircuitBreaker\CircuitBreaker::class)->get('payment-gateway');
$breaker->setState('open'); // Force open state for testing
Misconfigured Timeouts:
timeout too low may cause false positives (e.g., slow but not failed requests).timeout based on the dependency’s SLA.State Persistence:
'driver' => 'redis',
'prefix' => 'circuit_breaker_',
Blocking Calls:
Exception Handling:
HttpException, ConnectException) should increment failure counts.shouldCountAsFailure callback in config:
'breakers' => [
'payment-gateway' => [
'should_count_as_failure' => function ($exception) {
return $exception instanceof \Symfony\Component\HttpKernel\Exception\HttpException
&& $exception->getStatusCode() >= 500;
},
],
],
Check State: Inspect the breaker state manually:
$breaker = app(\Webrek\CircuitBreaker\CircuitBreaker::class)->get('payment-gateway');
dd($breaker->getState()); // Returns ['state', 'failures', 'next_reset']
Log Failures:
Enable debug logging in config/circuit-breaker.php:
'log_failures' => true,
Reset Manually: Reset a breaker programmatically (e.g., after a service restart):
app(\Webrek\CircuitBreaker\CircuitBreaker::class)->get('payment-gateway')->reset();
Custom States: Extend the breaker to support custom states (e.g., "degraded"):
// In a service provider:
\Webrek\CircuitBreaker\CircuitBreaker::extend('degraded', function () {
return new \Webrek\CircuitBreaker\States\DegradedState();
});
Event Listeners: Listen for breaker state changes:
\Webrek\CircuitBreaker\Events\CircuitBreakerStateChanged::class => [
\App\Listeners\NotifyOpsTeam::class,
],
Dynamic Configuration: Override breaker settings at runtime:
CircuitBreaker::configure('payment-gateway', [
'max_failures' => 5,
'reset_timeout' => 60,
]);
How can I help you explore Laravel packages today?