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
This commit is contained in:
439
backups/docs-backup-20250731125004/framework/error-boundaries.md
Normal file
439
backups/docs-backup-20250731125004/framework/error-boundaries.md
Normal file
@@ -0,0 +1,439 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user