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

439 lines
11 KiB
Markdown

# 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
```php
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
```php
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
```php
// 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
```php
$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
```php
$result = $boundary->execute(
operation: fn() => $service->riskyOperation(),
fallback: fn() => $service->safeAlternative()
);
```
### Optional Execution (Returns null on failure)
```php
$result = $boundary->executeOptional(
operation: fn() => $service->optionalOperation(),
fallback: fn() => $service->defaultValue() // Optional fallback
);
```
### Default Value on Failure
```php
$result = $boundary->executeWithDefault(
operation: fn() => $service->getValue(),
defaultValue: 'default_value'
);
```
### Result Wrapper
```php
$result = $boundary->executeForResult(
operation: fn() => $service->operation()
);
if ($result->isSuccess()) {
$value = $result->getValue();
} else {
$error = $result->getError();
}
```
## Retry Strategies
### Fixed Delay
```php
RetryStrategy::FIXED // Same delay between retries
```
### Linear Backoff
```php
RetryStrategy::LINEAR // Linearly increasing delay
```
### Exponential Backoff
```php
RetryStrategy::EXPONENTIAL // Exponentially increasing delay
```
### Exponential with Jitter
```php
RetryStrategy::EXPONENTIAL_JITTER // Exponential + random jitter
```
## Circuit Breaker Pattern
```php
$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:
```php
$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
```php
$result = $boundary->executeWithTimeout(
operation: fn() => $longRunningService->process(),
fallback: fn() => 'Operation timed out',
timeoutSeconds: 30
);
```
## Parallel Operations
Execute multiple operations with individual boundaries:
```php
$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:
```php
// 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:
```env
# 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
```bash
# 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
```bash
# 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
```php
// 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
```php
// 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
```php
// 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
```php
$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
```php
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
```php
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
```php
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
```php
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