Examples
Real-world examples demonstrating retry patterns in various scenarios.
HTTP API Client
Section titled “HTTP API Client”A production-ready HTTP client with intelligent retry logic:
use Cline\Retry\Retry;use Cline\Retry\Strategy\ExponentialJitterBackoff;use GuzzleHttp\Client;use GuzzleHttp\Exception\RequestException;
class ResilientHttpClient{ private Client $client;
public function __construct() { $this->client = new Client(['timeout' => 30]); }
public function get(string $url, array $options = []): array { return Retry::times(5) ->withBackoff(ExponentialJitterBackoff::milliseconds(100)) ->withMaxDelay(30_000_000) // 30 seconds ->when(function ($exception, $attempt) use ($url) { if (!$exception instanceof RequestException) { return false; }
$code = $exception->getCode();
// Don't retry client errors (4xx) if ($code >= 400 && $code < 500) { return false; }
// Retry server errors (5xx) if ($code >= 500) { logger()->warning("Retrying request (attempt {$attempt})", [ 'url' => $url, 'code' => $code, ]); return true; }
// Retry connection errors return true; }) ->execute(fn() => $this->client->get($url, $options)->getBody()->getContents()); }}Database Transaction with Deadlock Handling
Section titled “Database Transaction with Deadlock Handling”Automatically retry transactions on deadlock:
use Cline\Retry\Retry;use Cline\Retry\Strategy\ExponentialBackoff;use Illuminate\Support\Facades\DB;
class BankingService{ public function transfer(int $fromAccountId, int $toAccountId, float $amount): void { Retry::times(10) ->withBackoff(ExponentialBackoff::milliseconds(50)) ->withMaxDelay(5_000_000) // 5 seconds ->when(fn($e) => str_contains($e->getMessage(), 'Deadlock') || str_contains($e->getMessage(), 'Lock wait timeout') ) ->execute(function () use ($fromAccountId, $toAccountId, $amount) { DB::transaction(function () use ($fromAccountId, $toAccountId, $amount) { // Lock accounts in consistent order to prevent deadlocks $ids = [$fromAccountId, $toAccountId]; sort($ids);
$accounts = Account::whereIn('id', $ids) ->lockForUpdate() ->get() ->keyBy('id');
$fromAccount = $accounts[$fromAccountId]; $toAccount = $accounts[$toAccountId];
if ($fromAccount->balance < $amount) { throw new InsufficientFundsException(); }
$fromAccount->balance -= $amount; $toAccount->balance += $amount;
$fromAccount->save(); $toAccount->save(); }); }); }}Queue Job Processor
Section titled “Queue Job Processor”Laravel queue job with retry logic:
use Cline\Retry\Retry;use Cline\Retry\Strategy\ExponentialBackoff;
class ProcessPayment implements ShouldQueue{ use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct( private int $orderId, private float $amount ) {}
public function handle(PaymentGateway $gateway): void { Retry::times(5) ->withBackoff(ExponentialBackoff::seconds(2)) ->withMaxDelay(300_000_000) // 5 minutes ->when(function ($exception, $attempt) { // Don't retry validation errors if ($exception instanceof ValidationException) { logger()->error('Payment validation failed', [ 'order_id' => $this->orderId, 'error' => $exception->getMessage(), ]); return false; }
// Don't retry insufficient funds if ($exception instanceof InsufficientFundsException) { return false; }
// Retry transient failures logger()->info("Retrying payment (attempt {$attempt})", [ 'order_id' => $this->orderId, ]); return true; }) ->execute(fn() => $gateway->charge($this->orderId, $this->amount)); }}File Upload to S3
Section titled “File Upload to S3”Upload files with automatic retry on failure:
use Cline\Retry\Retry;use Cline\Retry\Strategy\ExponentialJitterBackoff;use Aws\S3\S3Client;use Aws\Exception\AwsException;
class FileUploader{ public function __construct(private S3Client $s3) {}
public function upload(string $localPath, string $bucket, string $key): array { return Retry::times(5) ->withBackoff(ExponentialJitterBackoff::seconds(1)) ->withMaxDelay(60_000_000) // 1 minute ->when(function ($exception, $attempt) { if (!$exception instanceof AwsException) { return false; }
$errorCode = $exception->getAwsErrorCode();
// Don't retry on permanent errors if (in_array($errorCode, ['NoSuchBucket', 'AccessDenied'])) { return false; }
// Retry on throttling if ($errorCode === 'RequestThrottled') { logger()->info("S3 throttled, retrying (attempt {$attempt})"); return true; }
// Retry on 5xx errors return $exception->getStatusCode() >= 500; }) ->execute(fn() => $this->s3->putObject([ 'Bucket' => $bucket, 'Key' => $key, 'SourceFile' => $localPath, 'ServerSideEncryption' => 'AES256', ])->toArray()); }}External API with Rate Limiting
Section titled “External API with Rate Limiting”Handle rate-limited third-party APIs:
use Cline\Retry\Retry;use Cline\Retry\Strategy\DecorrelatedJitterBackoff;
class WeatherApiClient{ private string $apiKey; private string $baseUrl = 'https://api.weather.example.com';
public function getForecast(string $city): array { return Retry::times(5) ->withBackoff(DecorrelatedJitterBackoff::milliseconds(100, 30_000)) ->when(function ($exception, $attempt) { // Rate limit - wait longer if ($exception->getCode() === 429) { logger()->info('Rate limited by weather API', [ 'attempt' => $attempt, ]); return true; }
// Service unavailable - retry if ($exception->getCode() === 503) { return true; }
// Timeout - retry if ($exception instanceof TimeoutException) { return true; }
return false; }) ->execute(function () use ($city) { $response = file_get_contents( "{$this->baseUrl}/forecast?city={$city}&key={$this->apiKey}", false, stream_context_create([ 'http' => ['timeout' => 10] ]) );
return json_decode($response, true); }); }}Microservice Communication
Section titled “Microservice Communication”Resilient communication between microservices:
use Cline\Retry\Retry;use Cline\Retry\Strategy\ExponentialJitterBackoff;use GuzzleHttp\Client;
class PaymentServiceClient{ private Client $client; private CircuitBreaker $circuitBreaker;
public function __construct() { $this->client = new Client([ 'base_uri' => config('services.payment.url'), 'timeout' => 30, ]); }
public function createPayment(array $data): array { return Retry::times(3) ->withBackoff(ExponentialJitterBackoff::milliseconds(200)) ->withMaxDelay(10_000_000) // 10 seconds ->when(function ($exception, $attempt) { // Circuit breaker check if ($this->circuitBreaker->isOpen()) { logger()->error('Payment service circuit breaker open'); return false; }
// Track failure $this->circuitBreaker->recordFailure();
// Don't retry validation errors if ($exception->getCode() >= 400 && $exception->getCode() < 500) { return false; }
logger()->warning("Payment service retry (attempt {$attempt})", [ 'error' => $exception->getMessage(), ]);
return true; }) ->execute(function () use ($data) { $response = $this->client->post('/payments', [ 'json' => $data, 'headers' => [ 'X-Idempotency-Key' => $data['idempotency_key'], ], ]);
$this->circuitBreaker->recordSuccess();
return json_decode($response->getBody(), true); }); }}Cache-Aside Pattern
Section titled “Cache-Aside Pattern”Implement cache-aside pattern with retry:
use Cline\Retry\Retry;use Cline\Retry\Strategy\LinearBackoff;
class UserRepository{ public function find(int $id): User { $cacheKey = "user:{$id}";
// Try cache first $cached = $this->cache->get($cacheKey); if ($cached !== null) { return $cached; }
// Fetch from database with retry $user = Retry::times(3) ->withBackoff(LinearBackoff::milliseconds(100)) ->execute(fn() => User::findOrFail($id));
// Store in cache with retry Retry::times(3) ->withBackoff(LinearBackoff::milliseconds(50)) ->when(fn($e) => $e instanceof RedisException) ->execute(fn() => $this->cache->put($cacheKey, $user, 3600));
return $user; }}Functional Pipeline
Section titled “Functional Pipeline”Using retry in functional pipelines:
use function Cline\Retry\retry;use Cline\Retry\Strategy\ExponentialBackoff;
// Create reusable retrier$retrier = retry(5, ExponentialBackoff::milliseconds(100));
// Use in pipelinefunction processDataFeed(string $url): array{ global $retrier;
return pipe( $url, $retrier(file_get_contents(...)), json_decode(...), fn($data) => array_filter($data, fn($item) => $item['active']), fn($data) => array_map(fn($item) => [ 'id' => $item['id'], 'name' => $item['name'], 'processed_at' => time(), ], $data) );}Common Patterns Summary
Section titled “Common Patterns Summary”| Use Case | Strategy | Max Delay | Retry Condition |
|---|---|---|---|
| HTTP APIs | ExponentialJitter | 30s | Don’t retry 4xx |
| Databases | Exponential | 5s | Only deadlocks |
| File Systems | Linear | 10s | Lock/network issues |
| Cloud Services | DecorrelatedJitter | 60s | Respect rate limits |
| Queues | Exponential | 5m | Selective by type |
| Microservices | ExponentialJitter | 10s | Circuit breaker |