leyton/laravel-circuit-breaker

The Laravel implementation for The Circuit breaker pattern

1.0 2024-05-17 08:58 UTC

This package is auto-updated.

Last update: 2024-10-09 15:14:24 UTC


README

The Circuit Breaker Pattern is essential for ensuring your software's resilience. It prevents failures from spreading, maintaining operational stability even when services encounter issues. By providing visual feedback and improving user experience, it keeps your application running smoothly. Additionally, it simplifies maintenance and troubleshooting, enabling quicker problem resolution. Overall, integrating the Circuit Breaker Pattern is crucial for enhancing reliability and user satisfaction.

Circuit Breaker states

You can find more detailes about this pattern here Circuit Breaker Pattern

Installation

composer require leyton/laravel-circuit-breaker

After the installation make sure to publish the assets

php artisan vendor:publish --provider="Leyton\LaravelCircuitBreaker\LaravelCircuitBreakerServiceProvider"

You will find the config/circuit-breaker.php file containing all the configurations needed.

<?php

return [
  'threshold' => 10, // number of trials to pass from half-open to closed/open and from closed to half-open
  'available_after_seconds' => 10, // the seconds it takes while in the open status
  'driver' => 'redis', // the cache store driver
];

Usage

The package provides you with a straightforward API to use.

<?php

use Leyton\LaravelCircuitBreaker\Circuit;
use Leyton\LaravelCircuitBreaker\Exceptions\RequestFailedException;


function goToGoogle(){
  try{
    $response = Http::get("https://gooogle.com");

    if($response->status() === 500){
        throw new RequestFailedException();
    }

  return "all is good";
  }catch(\Exception $exception){
    throw new RequestFailedException();
  }
}

// The Circuit is resolved out of the service container

$circuit = app()->make(Circuit::class);

//The run method expects the service name and the function that wraps the service
//It should throw the RequestFailedException when the service is not responding as expected
$packet =  $circuit->run("go-to-google", fn() => goToGoogle());

The packet object holds the result of the callback and the status of the service

Leyton\LaravelCircuitBreaker\Transporters\Packet {#2939
    +result: "all is good",
    +status: Leyton\LaravelCircuitBreaker\CircuitStatus {#2943
        +name: "CLOSED",
        +value: "closed",
    },
    +success: true,
}

One of the benefits of this pattern is to prevent the system from performing unnecessary actions when there are multiple transactions. This is an example of the usage

<?php

namespace App\Http\Controllers;

use Leyton\LaravelCircuitBreaker\Circuit;
use App\Services\LocationServiceClient;
use App\Services\PaymentGatewayClient;
use Illuminate\Http\Request;
use Exception;


class MakeOrderController extends Controller
{
    public function __construct(
        protected LocationServiceClient $locationServiceClient,
        protected PaymentGatewayClient $paymentGatewayClient,
        protected Circuit $Circuit,
    ) {
        
    }

    public function __invoke(Request $request)
    {
        $location = $request->get('location');
        $paymentDetails = $request->get('payment_details');
        $client = $request->get('client');

        if(!$this->circuit->available(['location-service', 'payment-service'])){
            return response()->json([
                'message' => 'Services are un-available, please retry later'
            ]);
        }
        
        $withdrawalPoint = $this->circuit->run("location-service", fn() => $this->locationServiceClient->getNearWithdrawalPoint($location));
        
        $payment = $this->circuit->run(
                "payment-service", 
                fn() => $this->paymentGatewayClient->processPayment($client, $order, $withdrawalPoint->result)
        );

    // ...
        return response()->json($data);
    }
}