diff --git a/README.md b/README.md index ae10e4a..5032dbc 100644 --- a/README.md +++ b/README.md @@ -91,20 +91,15 @@ $breaker = CircuitBreaker::for('3rd-party-service') ]); ``` -## Interceptors +## Excluding some failures -You can configure the circuit breaker to fail based on specific conditions or to skip certain types of failures: +Change the circuit breaker behavior by configuring it to exclude certain types of failures: ```php use Stfn\CircuitBreaker\CircuitBreaker; $breaker = CircuitBreaker::for('3rd-party-service') - ->failWhen(function ($result) { - return $result->status() >= 400; - }); - -$breaker = CircuitBreaker::for('3rd-party-service') - ->skipFailure(function ($exception) { + ->skipFailureCount(function ($exception) { return $exception->getCode() < 500; }); ``` diff --git a/src/CircuitBreaker.php b/src/CircuitBreaker.php index 3642e80..9df32b5 100755 --- a/src/CircuitBreaker.php +++ b/src/CircuitBreaker.php @@ -36,12 +36,7 @@ class CircuitBreaker /** * @var \Closure|null */ - protected \Closure|null $failWhenCallback = null; - - /** - * @var \Closure|null - */ - protected \Closure|null $skipFailureCallback = null; + protected \Closure|null $skipFailureCountCallback = null; /** * @param string $name @@ -200,20 +195,9 @@ public function withListeners(array $listeners): self * @param \Closure $closure * @return $this */ - public function skipFailure(\Closure $closure) + public function skipFailureCount(\Closure $closure) { - $this->skipFailureCallback = $closure; - - return $this; - } - - /** - * @param \Closure $closure - * @return $this - */ - public function failWhen(\Closure $closure) - { - $this->failWhenCallback = $closure; + $this->skipFailureCountCallback = $closure; return $this; } @@ -264,16 +248,8 @@ public function getListeners(): array /** * @return \Closure|null */ - public function getFailWhenCallback(): ?\Closure - { - return $this->failWhenCallback; - } - - /** - * @return \Closure|null - */ - public function getSkipFailureCallback(): ?\Closure + public function getSkipFailureCountCallback(): ?\Closure { - return $this->skipFailureCallback; + return $this->skipFailureCountCallback; } } diff --git a/src/Config.php b/src/Config.php index 7fa9e8e..33d2133 100644 --- a/src/Config.php +++ b/src/Config.php @@ -30,7 +30,7 @@ class Config * @param int $failureThreshold * @param int $recoveryTime * @param int $sampleDuration - * @param int $consecutiveSuccesses + * @param int $consecutiveSuccess */ public function __construct( int $failureThreshold = 5, diff --git a/src/Exceptions/CircuitOpenException.php b/src/Exceptions/CircuitOpenException.php index aa8e7d0..29c3a5a 100644 --- a/src/Exceptions/CircuitOpenException.php +++ b/src/Exceptions/CircuitOpenException.php @@ -10,6 +10,6 @@ class CircuitOpenException extends \Exception */ public static function make($service) { - return new self("Circuit breaker for service '{$service}' is open"); + return new self("Circuit breaker for service {$service} is open."); } } diff --git a/src/Exceptions/FailOnSuccessException.php b/src/Exceptions/FailOnSuccessException.php deleted file mode 100644 index 6df7a47..0000000 --- a/src/Exceptions/FailOnSuccessException.php +++ /dev/null @@ -1,11 +0,0 @@ -breaker->getStorage(); + $failures = $this->breaker->getStorage()->getCounter()->numberOfFailures(); - $storage->incrementFailure(); + $threshold = $this->breaker->getConfig()->failureThreshold; - $config = $this->breaker->getConfig(); - - if ($storage->getCounter()->numberOfFailures() >= $config->failureThreshold) { + if ($failures >= $threshold) { $this->breaker->openCircuit(); throw CircuitOpenException::make($this->breaker->getName()); diff --git a/src/StateHandlers/StateHandler.php b/src/StateHandlers/StateHandler.php index ab87921..0628cde 100644 --- a/src/StateHandlers/StateHandler.php +++ b/src/StateHandlers/StateHandler.php @@ -3,7 +3,6 @@ namespace Stfn\CircuitBreaker\StateHandlers; use Stfn\CircuitBreaker\CircuitBreaker; -use Stfn\CircuitBreaker\Exceptions\FailOnSuccessException; class StateHandler { @@ -64,14 +63,16 @@ public function beforeCall(\Closure $action, ...$args) */ public function handleFailure(\Exception $exception) { - if (is_callable($this->breaker->getSkipFailureCallback())) { - $shouldSkip = call_user_func($this->breaker->getSkipFailureCallback(), $exception); + if (is_callable($this->breaker->getSkipFailureCountCallback())) { + $shouldSkip = call_user_func($this->breaker->getSkipFailureCountCallback(), $exception); if ($shouldSkip) { - return; + throw $exception; } } + $this->breaker->getStorage()->incrementFailure(); + foreach ($this->breaker->getListeners() as $listener) { $listener->onFail($this->breaker, $exception); } @@ -84,18 +85,9 @@ public function handleFailure(\Exception $exception) /** * @param $result * @return void - * @throws FailOnSuccessException */ public function handleSucess($result) { - if (is_callable($this->breaker->getFailWhenCallback())) { - $shouldFail = call_user_func($this->breaker->getFailWhenCallback(), $result); - - if ($shouldFail) { - throw FailOnSuccessException::make(); - } - } - $this->onSucess(); foreach ($this->breaker->getListeners() as $listener) { diff --git a/tests/CircuitBreakerTest.php b/tests/CircuitBreakerTest.php index 8f0eb9e..5702213 100644 --- a/tests/CircuitBreakerTest.php +++ b/tests/CircuitBreakerTest.php @@ -29,7 +29,6 @@ public function test_if_it_can_handle_function_success() }); $this->assertEquals($object, $result); - $this->assertTrue($breaker->isClosed()); } @@ -202,15 +201,17 @@ public function beforeCall(CircuitBreaker $breaker, \Closure $action, ...$args): $this->assertEquals(2, $object->count); } - public function test_if_it_can_skip_some_exception() + public function test_if_it_can_skip_count_of_some_exceptions() { $testException = new class () extends \Exception {}; $breaker = CircuitBreaker::for('test-service') - ->skipFailure(function (\Exception $exception) use ($testException) { + ->skipFailureCount(function (\Exception $exception) use ($testException) { return $exception instanceof $testException; }); + $this->expectException(\Exception::class); + $breaker->call(function () use ($testException) { throw $testException; }); @@ -218,29 +219,6 @@ public function test_if_it_can_skip_some_exception() $this->assertEquals(0, $breaker->getStorage()->getCounter()->numberOfFailures()); } - public function test_if_it_can_fail_even_without_exception() - { - $breaker = CircuitBreaker::for('test-service') - ->withOptions([ - 'failure_threshold' => 3, - ]) - ->failWhen(function ($result) { - return $result instanceof \stdClass; - }); - - foreach (range(1, 3) as $i) { - try { - $breaker->call(fn () => new \stdClass()); - } catch (\Exception) { - - } - } - - // Make sure that number of failures is reset to zero - $this->assertEquals(0, $breaker->getStorage()->getCounter()->numberOfFailures()); - $this->assertTrue($breaker->isOpen()); - } - public function test_if_it_can_force_open_circuit() { $breaker = CircuitBreaker::for('test-service'); diff --git a/tests/Storage/RedisStorageTest.php b/tests/Storage/RedisStorageTest.php index 869fa62..3fe351b 100644 --- a/tests/Storage/RedisStorageTest.php +++ b/tests/Storage/RedisStorageTest.php @@ -47,7 +47,6 @@ public function test_if_increment_failure_will_increase_number_of_failures() $storage->incrementFailure(); - $this->assertEquals(2, $storage->getCounter()->numberOfFailures()); }