Files
michaelschiemer/backups/docs-backup-20250731125004/framework/error-boundaries.md
Michael Schiemer 55a330b223 Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug
- Add DISCOVERY_SHOW_PROGRESS=true
- Temporary changes for debugging InitializerProcessor fixes on production
2025-08-11 20:13:26 +02:00

11 KiB

Error Boundaries

Error Boundaries provide graceful degradation and prevent cascading failures in the application. They act as a safety net that catches errors and provides fallback functionality instead of letting the entire system fail.

Overview

The Error Boundary system implements multiple patterns for resilient error handling:

  • Fallback Mechanisms - Provide alternative functionality when operations fail
  • Retry Strategies - Automatically retry failed operations with configurable strategies
  • Circuit Breaker Pattern - Prevent repeated calls to failing services
  • Bulk Operations - Handle partial failures in batch processing
  • Timeout Protection - Prevent long-running operations from blocking the system

Basic Usage

Simple Error Boundary

use App\Framework\ErrorBoundaries\ErrorBoundary;
use App\Framework\ErrorBoundaries\BoundaryConfig;

$boundary = new ErrorBoundary('user_service', BoundaryConfig::externalService());

$result = $boundary->execute(
    operation: fn() => $userService->getUser($id),
    fallback: fn() => $this->getCachedUser($id)
);

Factory Pattern

use App\Framework\ErrorBoundaries\ErrorBoundaryFactory;

$factory = $container->get(ErrorBoundaryFactory::class);

// Create boundary for different contexts
$dbBoundary = $factory->createForDatabase('user_queries');
$apiBoundary = $factory->createForExternalService('payment_api');
$uiBoundary = $factory->createForUI('user_dashboard');

Configuration

Predefined Configurations

// Critical operations - maximum resilience
BoundaryConfig::critical()

// External services - network-aware retries
BoundaryConfig::externalService()

// Database operations - transaction-safe retries
BoundaryConfig::database()

// UI components - fast failure for user experience
BoundaryConfig::ui()

// Background jobs - long retry cycles
BoundaryConfig::backgroundJob()

// Development - permissive for debugging
BoundaryConfig::development()

// Fail fast - no retries
BoundaryConfig::failFast()

Custom Configuration

$config = new BoundaryConfig(
    maxRetries: 3,
    retryStrategy: RetryStrategy::EXPONENTIAL_JITTER,
    baseDelay: Duration::fromMilliseconds(100),
    maxDelay: Duration::fromSeconds(5),
    circuitBreakerEnabled: true,
    circuitBreakerThreshold: 5,
    circuitBreakerTimeout: Duration::fromMinutes(1),
    maxBulkErrorRate: 0.3,
    enableMetrics: true,
    enableTracing: false
);

Execution Strategies

Standard Execution with Fallback

$result = $boundary->execute(
    operation: fn() => $service->riskyOperation(),
    fallback: fn() => $service->safeAlternative()
);

Optional Execution (Returns null on failure)

$result = $boundary->executeOptional(
    operation: fn() => $service->optionalOperation(),
    fallback: fn() => $service->defaultValue() // Optional fallback
);

Default Value on Failure

$result = $boundary->executeWithDefault(
    operation: fn() => $service->getValue(),
    defaultValue: 'default_value'
);

Result Wrapper

$result = $boundary->executeForResult(
    operation: fn() => $service->operation()
);

if ($result->isSuccess()) {
    $value = $result->getValue();
} else {
    $error = $result->getError();
}

Retry Strategies

Fixed Delay

RetryStrategy::FIXED // Same delay between retries

Linear Backoff

RetryStrategy::LINEAR // Linearly increasing delay

Exponential Backoff

RetryStrategy::EXPONENTIAL // Exponentially increasing delay

Exponential with Jitter

RetryStrategy::EXPONENTIAL_JITTER // Exponential + random jitter

Circuit Breaker Pattern

$config = new BoundaryConfig(
    circuitBreakerEnabled: true,
    circuitBreakerThreshold: 5, // Open after 5 failures
    circuitBreakerTimeout: Duration::fromMinutes(2) // Try again after 2 minutes
);

$result = $boundary->executeWithCircuitBreaker(
    operation: fn() => $externalService->call(),
    fallback: fn() => $this->getCachedResponse()
);

Bulk Operations

Handle batch processing with partial failure tolerance:

$items = [1, 2, 3, 4, 5];

$result = $boundary->executeBulk($items, function($item) {
    if ($item % 2 === 0) {
        throw new Exception("Even numbers fail");
    }
    return $item * 2;
});

echo "Processed: {$result->getProcessedCount()}/{$result->getTotalCount()}\n";
echo "Success rate: {$result->getSuccessRate()}%\n";

foreach ($result->getResults() as $key => $value) {
    echo "Item {$key}: {$value}\n";
}

foreach ($result->getErrors() as $key => $error) {
    echo "Error {$key}: {$error->getMessage()}\n";
}

Timeout Protection

$result = $boundary->executeWithTimeout(
    operation: fn() => $longRunningService->process(),
    fallback: fn() => 'Operation timed out',
    timeoutSeconds: 30
);

Parallel Operations

Execute multiple operations with individual boundaries:

$operations = [
    'user_data' => fn() => $userService->getData(),
    'preferences' => fn() => $prefsService->getPreferences(),
    'notifications' => fn() => $notificationService->getCount()
];

$results = $boundary->executeParallel($operations);

foreach ($results as $name => $result) {
    if ($result->isSuccess()) {
        $data[$name] = $result->getValue();
    } else {
        $data[$name] = null; // Or default value
    }
}

HTTP Middleware Integration

Automatic error boundary protection for HTTP requests:

// In middleware registration
$app->addMiddleware(ErrorBoundaryMiddleware::class);

The middleware automatically:

  • Creates boundaries based on route patterns
  • Provides JSON fallback responses for API routes
  • Provides HTML error pages for web routes
  • Logs failures for monitoring

Environment Configuration

Configure boundaries via environment variables:

# Global settings
ERROR_BOUNDARY_ENABLED=true

# Route-specific configuration
ERROR_BOUNDARY_ROUTE_API_USER_MAX_RETRIES=5
ERROR_BOUNDARY_ROUTE_API_USER_CIRCUIT_BREAKER_ENABLED=true
ERROR_BOUNDARY_ROUTE_API_USER_BASE_DELAY_MS=200

Console Commands

Test Error Boundaries

# Test basic functionality
php console.php boundary:test basic

# Test retry strategies
php console.php boundary:test retry

# Test circuit breaker
php console.php boundary:test circuit

# Test bulk operations
php console.php boundary:test bulk

Monitor Circuit Breakers

# Show circuit breaker statistics
php console.php boundary:stats

# Reset specific circuit breaker
php console.php boundary:reset user_service

# Reset all circuit breakers
php console.php boundary:reset

Best Practices

1. Choose Appropriate Configurations

// API endpoints - use external service config
$apiBoundary = $factory->createForExternalService('payment_api');

// Database queries - use database config
$dbBoundary = $factory->createForDatabase('user_queries');

// UI components - use UI config for fast failures
$uiBoundary = $factory->createForUI('dashboard_widget');

2. Meaningful Fallbacks

// Good - provides useful fallback
$boundary->execute(
    operation: fn() => $service->getLiveData(),
    fallback: fn() => $service->getCachedData()
);

// Avoid - fallback provides no value
$boundary->execute(
    operation: fn() => $service->getData(),
    fallback: fn() => null
);

3. Monitor Circuit Breakers

// Set up alerting for circuit breaker state changes
$boundary->executeWithCircuitBreaker(
    operation: fn() => $service->call(),
    fallback: function() {
        $this->logger->warning('Circuit breaker activated for service');
        return $this->getFallbackData();
    }
);

4. Handle Bulk Operations Appropriately

$result = $boundary->executeBulk($items, $processor);

// Check if too many failures occurred
if ($result->getErrorRate() > 50) {
    $this->logger->error('High error rate in bulk operation');
    // Consider stopping or alerting
}

Integration Examples

With Repositories

class UserRepository
{
    public function __construct(
        private ErrorBoundaryFactory $boundaryFactory,
        private DatabaseConnection $db
    ) {}
    
    public function findById(int $id): ?User
    {
        $boundary = $this->boundaryFactory->createForDatabase('user_find');
        
        return $boundary->executeOptional(
            operation: fn() => $this->db->query('SELECT * FROM users WHERE id = ?', [$id]),
            fallback: fn() => $this->getCachedUser($id)
        );
    }
}

With External APIs

class PaymentService
{
    public function processPayment(Payment $payment): PaymentResult
    {
        $boundary = $this->boundaryFactory->createForExternalService('payment_gateway');
        
        return $boundary->execute(
            operation: fn() => $this->gateway->process($payment),
            fallback: fn() => $this->queueForLaterProcessing($payment)
        );
    }
}

With Background Jobs

class EmailJob
{
    public function handle(): void
    {
        $boundary = $this->boundaryFactory->createForBackgroundJob('email_sending');
        
        $boundary->executeBulk($this->emails, function($email) {
            $this->mailer->send($email);
        });
    }
}

Error Handling

Error boundaries can throw specific exceptions:

  • BoundaryFailedException - When both operation and fallback fail
  • BoundaryTimeoutException - When operations exceed timeout limits
try {
    $result = $boundary->execute($operation, $fallback);
} catch (BoundaryFailedException $e) {
    $this->logger->error('Boundary failed completely', [
        'boundary' => $e->getBoundaryName(),
        'original_error' => $e->getOriginalException()?->getMessage(),
        'fallback_error' => $e->getFallbackException()?->getMessage(),
    ]);
} catch (BoundaryTimeoutException $e) {
    $this->logger->warning('Operation timed out', [
        'boundary' => $e->getBoundaryName(),
        'execution_time' => $e->getExecutionTime(),
        'timeout_limit' => $e->getTimeoutLimit(),
    ]);
}

Performance Considerations

  1. Circuit Breakers - Use file-based storage for simplicity, consider Redis for high-traffic applications
  2. Retry Delays - Use jitter to avoid thundering herd problems
  3. Bulk Operations - Set appropriate error rate thresholds to prevent resource exhaustion
  4. Timeouts - PHP's synchronous nature limits true timeout implementation

Security Considerations

  1. Information Disclosure - Ensure fallbacks don't leak sensitive information
  2. Resource Exhaustion - Configure appropriate timeouts and retry limits
  3. Circuit Breaker State - Protect circuit breaker state files from unauthorized access