farzai / breaker
A PHP circuit breaker implementation for resilient applications
Fund package maintenance!
parsilver
Requires
- php: ^8.2
Requires (Dev)
- laravel/pint: ^1.0
- pestphp/pest: ^3.5
- phpstan/phpstan: ^1.10
- spatie/ray: ^1.28
README
A PHP Circuit Breaker implementation for building resilient applications.
Installation
composer require farzai/breaker
Requirements
- PHP 8.2 or higher
Basic Usage
use Farzai\Breaker\CircuitBreaker; use Farzai\Breaker\Exceptions\CircuitOpenException; // Create a circuit breaker with default settings $breaker = new CircuitBreaker('service-name'); try { $result = $breaker->call(function () { // Your code that might fail return callExternalService(); }); } catch (CircuitOpenException $e) { // Circuit is open due to previous failures return getBackupData(); } catch (\Exception $e) { // Handle other exceptions return null; }
Advanced Configuration
// Create a circuit breaker with custom configuration $breaker = new CircuitBreaker('service-name', [ 'failure_threshold' => 5, // Number of failures before opening circuit 'timeout' => 30, // Seconds to wait before checking if service is back 'success_threshold' => 2, // Success calls needed to close the circuit again ]);
Using Fallbacks
use Farzai\Breaker\CircuitBreaker; use Farzai\Breaker\Exceptions\CircuitOpenException; $breaker = new CircuitBreaker('service-name'); // Execute with a fallback $result = $breaker->callWithFallback( function () { // Primary function that might fail return callExternalService(); }, function ($exception, $circuitBreaker) { // Fallback function with access to the exception and circuit breaker if ($exception instanceof CircuitOpenException) { // Circuit is open, use cached data or alternative service return getCachedData(); } // Handle other types of failures return getDefaultResponse(); } );
Event Listeners
use Farzai\Breaker\CircuitBreaker; $breaker = new CircuitBreaker('service-name'); // Listen to state transitions $breaker->onStateChange(function ($newState, $oldState, $circuitBreaker) { echo "Circuit state changed from {$oldState} to {$newState}"; }); // Listen to specific state transitions $breaker->onOpen(function ($circuitBreaker) { // Circuit opened - notify administrators sendAlertToAdmin("Service {$circuitBreaker->getServiceKey()} is down!"); }); $breaker->onHalfOpen(function ($circuitBreaker) { // Circuit is testing the service logEvent("Testing if service is back online"); }); $breaker->onClose(function ($circuitBreaker) { // Circuit closed - service is healthy again sendStatusUpdate("Service recovered successfully"); }); // Listen to call success and failures $breaker->onSuccess(function ($result, $circuitBreaker) { // Track successful calls incrementMetric("service.success"); }); // Remove a listener when no longer needed $listenerId = $breaker->onSuccess(function () { /* ... */ }); $breaker->removeListener($listenerId);
Persistent Storage
By default, circuit state is stored in memory. For production, use file storage:
use Farzai\Breaker\CircuitBreaker; use Farzai\Breaker\Storage\FileStorage; // Create a file storage with a specific directory $storage = new FileStorage(__DIR__ . '/storage/circuit-breaker'); // Pass storage to circuit breaker $breaker = new CircuitBreaker('service-name', [], $storage);
Circuit Breaker States
The circuit breaker operates in three states:
- CLOSED: All requests pass through to the service. This is the default state when everything is working normally.
- OPEN: Requests fail fast without calling the service. This happens after the failure threshold is reached, protecting your system from cascading failures.
- HALF-OPEN: After the timeout period, the circuit allows a limited number of test requests to check if the service has recovered.
Best Practices
Service Isolation
Create separate circuit breakers for different services:
$userServiceBreaker = new CircuitBreaker('user-service'); $paymentServiceBreaker = new CircuitBreaker('payment-service'); $notificationServiceBreaker = new CircuitBreaker('notification-service');
Appropriate Thresholds
Configure thresholds based on service characteristics:
// Critical service with stricter thresholds $criticalBreaker = new CircuitBreaker('critical-service', [ 'failure_threshold' => 2, // Open after just 2 failures 'timeout' => 60, // Wait longer before testing again 'success_threshold' => 3, // Require more successes to restore ]); // Non-critical service with more lenient settings $nonCriticalBreaker = new CircuitBreaker('non-critical-service', [ 'failure_threshold' => 10, // Allow more failures 'timeout' => 15, // Test again quickly 'success_threshold' => 1, // Close after first success ]);
Always Use Fallbacks
Always provide fallbacks for critical operations:
$result = $breaker->callWithFallback( function () { return fetchDataFromPrimarySource(); }, function ($exception) { if ($exception instanceof CircuitOpenException) { return fetchFromCache(); } // For network errors, use backup service return fetchFromBackupService(); } );
Monitoring Circuit Health
Use event listeners to monitor circuit health:
// Log all state transitions $breaker->onStateChange(function ($newState, $oldState, $breaker) use ($logger) { $logger->info("Circuit '{$breaker->getServiceKey()}' changed from {$oldState} to {$newState}"); }); // Alert on circuit open $breaker->onOpen(function ($breaker) use ($alertService) { $alertService->sendAlert( "Circuit breaker for {$breaker->getServiceKey()} has opened after " . "{$breaker->getFailureThreshold()} consecutive failures." ); });
Circuit Breaker Pattern Integration
Integrate circuit breakers within your application architecture:
class ApiClient { private $httpClient; private $circuitBreaker; private $cache; public function __construct(HttpClientInterface $httpClient, CacheInterface $cache) { $this->httpClient = $httpClient; $this->cache = $cache; $this->circuitBreaker = new CircuitBreaker('api-client'); // Set up monitoring $this->setupEventListeners(); } public function fetchData($id) { return $this->circuitBreaker->callWithFallback( function () use ($id) { $response = $this->httpClient->get("/data/{$id}"); // Cache successful responses $this->cache->set("data_{$id}", $response, 3600); return $response; }, function ($exception) use ($id) { // Return cached data as fallback return $this->cache->get("data_{$id}"); } ); } private function setupEventListeners() { // Add event listeners for monitoring } }
License
This project is licensed under the MIT License - see the LICENSE file for details.