Skip to content
advanced Phase 100 · Senior Practices

Failure Handling Patterns

Failure handling patterns including graceful degradation, error boundaries, retry strategies, and circuit breakers in Magento

45m
0 problems
Topic Progress 0%

Graceful Degradation

Degradation Strategies

// Service unavailable - fallback to cache
class ProductPriceService
{
    public function getPrice(ProductId $productId): Money
    {
        try {
            return $this->priceRepository->get($productId);
        } catch (ServiceUnavailableException $e) {
            // Fallback to cached price
            return $this->cache->get('price_' . $productId->getValue()) ?? new Money(0, 'USD');
        }
    }
}

Feature Degradation

// Disable non-critical features during failure
class RecommendationService
{
    public function getRecommendations(ProductId $productId): array
    {
        try {
            return $this->mlService->getRecommendations($productId);
        } catch (Exception $e) {
            // Degrade: return empty recommendations
            $this->logger->warning('ML service unavailable, degrading recommendations');
            return [];
        }
    }
}

Content Degradation

// Show cached/static content when dynamic fails
class ProductPageController
{
    public function execute()
    {
        try {
            $product = $this->productService->getProduct($id);
            return $this->renderDynamic($product);
        } catch (Exception $e) {
            // Fallback to cached page
            $cached = $this->cache->get('product_page_' . $id);
            if ($cached) {
                return $this->renderCached($cached);
            }
            throw $e;
        }
    }
}

Key Takeaway

Graceful degradation provides reduced functionality when dependencies fail. Use cache fallbacks, disable non-critical features, and show cached content.

Error Boundaries

Module Error Boundaries

// Isolate failures by module
class OrderModule
{
    public function processOrder(Order $order): Result
    {
        try {
            $result = $this->orderService->process($order);
            return Result::success($result);
        } catch (Exception $e) {
            $this->logger->error('Order processing failed', [
                'order_id' => $order->getId(),
                'error' => $e->getMessage()
            ]);
            return Result::failure('Order processing temporarily unavailable');
        }
    }
}

Queue Error Boundaries

// Message queue error isolation
class OrderMessageHandler
{
    public function handle(Message $message): void
    {
        try {
            $order = $this->serializer->unserialize($message->getBody());
            $this->orderProcessor->process($order);
        } catch (DeserializationException $e) {
            // Message format error - don't retry
            $this->deadLetterQueue->send($message);
        } catch (BusinessException $e) {
            // Business error - don't retry
            $this->logger->warning('Business error', ['error' => $e->getMessage()]);
        } catch (Exception $e) {
            // Transient error - retry
            throw $e;
        }
    }
}

API Error Boundaries

// REST API error isolation
class ApiErrorHandler
{
    public function handleException(Exception $e): JsonResponse
    {
        return match(true) {
            $e instanceof AuthenticationException => 
                new JsonResponse(['error' => 'Unauthorized'], 401),
            $e instanceof AuthorizationException => 
                new JsonResponse(['error' => 'Forbidden'], 403),
            $e instanceof NotFoundException => 
                new JsonResponse(['error' => 'Not Found'], 404),
            $e instanceof ValidationException => 
                new JsonResponse(['error' => $e->getErrors()], 422),
            default => 
                new JsonResponse(['error' => 'Internal Server Error'], 500),
        };
    }
}

Key Takeaway

Error boundaries isolate failures to specific modules or services. Differentiate between transient and permanent errors for appropriate handling.

Retry Strategies

Exponential Backoff

// Exponential backoff retry
class RetryService
{
    public function retry(callable $fn, int $maxRetries = 3): mixed
    {
        $attempt = 0;
        
        while ($attempt < $maxRetries) {
            try {
                return $fn();
            } catch (Exception $e) {
                $attempt++;
                
                if ($attempt >= $maxRetries) {
                    throw $e;
                }
                
                // Exponential backoff with jitter
                $delay = (2 ** $attempt) * 1000;  // 2s, 4s, 8s
                $jitter = random_int(0, 1000);
                usleep(($delay + $jitter) * 1000);
            }
        }
    }
}

Retry Configuration

// Retry strategy configuration
$retryConfig = [
    'payment_service' => [
        'max_retries' => 3,
        'base_delay' => 1000,  // ms
        'max_delay' => 30000,  // ms
        'backoff_multiplier' => 2,
        'retryable_exceptions' => [
            ConnectionException::class,
            TimeoutException::class,
        ],
    ],
    'inventory_service' => [
        'max_retries' => 2,
        'base_delay' => 500,
        'retryable_exceptions' => [
            ServiceUnavailableException::class,
        ],
    ],
];

Idempotent Retries

// Ensure idempotency for retries
class IdempotentPaymentService
{
    public function processPayment(PaymentRequest $request): PaymentResult
    {
        $idempotencyKey = $request->getIdempotencyKey();
        
        // Check if already processed
        $existing = $this->paymentRepository->getByIdempotencyKey($idempotencyKey);
        if ($existing) {
            return $existing;
        }
        
        // Process payment
        $result = $this->paymentGateway->charge($request);
        
        // Store with idempotency key
        $this->paymentRepository->save($result, $idempotencyKey);
        
        return $result;
    }
}

Key Takeaway

Use exponential backoff with jitter for retries. Configure retryable exceptions. Ensure idempotency for safe retries.

Circuit Breaker Pattern

Circuit Breaker States

Closed (Normal) -> Open (Failing) -> Half-Open (Testing)
      |                                    |
      +------------------------------------+
                   (Success)

Circuit Breaker Implementation

class CircuitBreaker
{
    private int $failureCount = 0;
    private int $successCount = 0;
    private string $state = 'closed';
    private \DateTimeImmutable $lastFailureTime;
    
    private const FAILURE_THRESHOLD = 5;
    private const SUCCESS_THRESHOLD = 3;
    private const TIMEOUT = 60;  // seconds
    
    public function call(callable $fn): mixed
    {
        if ($this->state === 'open') {
            if ($this->shouldTryReset()) {
                $this->state = 'half-open';
            } else {
                throw new CircuitOpenException('Service unavailable');
            }
        }
        
        try {
            $result = $fn();
            $this->onSuccess();
            return $result;
        } catch (Exception $e) {
            $this->onFailure();
            throw $e;
        }
    }
    
    private function onSuccess(): void
    {
        $this->failureCount = 0;
        $this->successCount++;
        
        if ($this->state === 'half-open' && $this->successCount >= self::SUCCESS_THRESHOLD) {
            $this->state = 'closed';
        }
    }
    
    private function onFailure(): void
    {
        $this->failureCount++;
        $this->successCount = 0;
        $this->lastFailureTime = new \DateTimeImmutable();
        
        if ($this->failureCount >= self::FAILURE_THRESHOLD) {
            $this->state = 'open';
        }
    }
    
    private function shouldTryReset(): bool
    {
        return (time() - $this->lastFailureTime->getTimestamp()) >= self::TIMEOUT;
    }
}

Circuit Breaker Usage

class PaymentService
{
    private CircuitBreaker $circuitBreaker;
    
    public function processPayment(PaymentRequest $request): PaymentResult
    {
        return $this->circuitBreaker->call(function () use ($request) {
            return $this->paymentGateway->charge($request);
        });
    }
}

Key Takeaway

Circuit breaker prevents cascading failures. Open state fails fast. Half-open tests recovery. Closed state is normal operation.

Quiz

1. What is graceful degradation?

Question 1 options

2. What is exponential backoff?

Question 2 options

3. What states does a circuit breaker have?

Question 3 options

4. Why ensure idempotency for retries?

Question 4 options

5. What is an error boundary?

Question 5 options

Flashcards

Question

What is graceful degradation?

Answer

Reduced functionality when dependencies fail, using cache fallbacks

Question

What is exponential backoff?

Answer

Increasing delay between retries: 2s, 4s, 8s with jitter

Question

Circuit breaker states?

Answer

Closed (normal), Open (failing), Half-Open (testing)

Question

Why idempotency?

Answer

Prevent duplicate operations when retrying

Question

What is an error boundary?

Answer

Isolates failures to specific modules to prevent cascading

Question

When to use circuit breaker?

Answer

For external service calls that may fail or timeout

Revision Notes

Key Takeaways

  • 1. Graceful degradation provides reduced functionality during failures
  • 2. Error boundaries isolate failures to specific modules
  • 3. Exponential backoff with jitter for retry strategies
  • 4. Circuit breaker prevents cascading failures
  • 5. Ensure idempotency for safe retries

Interview Tips

  • Explain graceful degradation strategies
  • Describe circuit breaker pattern implementation
  • Discuss retry strategies with backoff
  • Explain error boundary design

Cheat Sheet

Failure Handling

Graceful Degradation:
Cache fallbacks
Disable non-critical features
Show cached content

Error Boundaries:
Isolate per module
Differentiate error types
Prevent cascading

Retry Strategies:
Exponential backoff
Jitter for randomness
Idempotent operations

Circuit Breaker:
Closed: Normal operation
Open: Failing, fail fast
Half-Open: Testing recovery