- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
439 lines
11 KiB
Markdown
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 |