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:
221
src/Framework/CircuitBreaker/HttpClientCircuitBreaker.php
Normal file
221
src/Framework/CircuitBreaker/HttpClientCircuitBreaker.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\CircuitBreaker;
|
||||
|
||||
use App\Framework\Core\ValueObjects\Duration;
|
||||
use App\Framework\HttpClient\ClientRequest;
|
||||
use App\Framework\HttpClient\ClientResponse;
|
||||
use App\Framework\HttpClient\Exception\ClientErrorException;
|
||||
use App\Framework\HttpClient\Exception\HttpClientException;
|
||||
use App\Framework\HttpClient\Exception\ServerErrorException;
|
||||
use App\Framework\HttpClient\HttpClient;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Circuit Breaker für HTTP Client Anfragen an externe Services
|
||||
*/
|
||||
final readonly class HttpClientCircuitBreaker
|
||||
{
|
||||
/**
|
||||
* @var array<string, CircuitBreakerConfig>
|
||||
*/
|
||||
private array $serviceConfigs;
|
||||
|
||||
public function __construct(
|
||||
private CircuitBreaker $circuitBreaker,
|
||||
private HttpClient $httpClient,
|
||||
array $serviceConfigs = []
|
||||
) {
|
||||
$this->serviceConfigs = $serviceConfigs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt HTTP Request mit Circuit Breaker Schutz aus
|
||||
*
|
||||
* @throws CircuitBreakerException|HttpClientException
|
||||
*/
|
||||
public function request(ClientRequest $request, ?string $serviceName = null): ClientResponse
|
||||
{
|
||||
$serviceName ??= $this->extractServiceName($request);
|
||||
$config = $this->serviceConfigs[$serviceName] ?? $this->getDefaultConfig();
|
||||
|
||||
return $this->circuitBreaker->execute(
|
||||
service: $serviceName,
|
||||
operation: function () use ($request) {
|
||||
$response = $this->httpClient->request($request);
|
||||
|
||||
// 5xx Status Codes als Fehler behandeln
|
||||
if ($response->getStatusCode() >= 500) {
|
||||
throw new ServerErrorException(
|
||||
"Server error: HTTP {$response->getStatusCode()}",
|
||||
$response->getStatusCode()
|
||||
);
|
||||
}
|
||||
|
||||
// 4xx Status Codes normalerweise nicht als Circuit Breaker Fehler behandeln
|
||||
// außer bei spezifischen Codes wie 429 (Rate Limit)
|
||||
if ($response->getStatusCode() === 429) {
|
||||
throw new ClientErrorException(
|
||||
"Rate limit exceeded: HTTP 429",
|
||||
429
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
},
|
||||
config: $config
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt GET Request mit Circuit Breaker Schutz aus
|
||||
*/
|
||||
public function get(string $url, array $headers = [], ?string $serviceName = null): ClientResponse
|
||||
{
|
||||
$request = new ClientRequest('GET', $url, $headers);
|
||||
|
||||
return $this->request($request, $serviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt POST Request mit Circuit Breaker Schutz aus
|
||||
*/
|
||||
public function post(string $url, mixed $body = null, array $headers = [], ?string $serviceName = null): ClientResponse
|
||||
{
|
||||
$request = new ClientRequest('POST', $url, $headers, $body);
|
||||
|
||||
return $this->request($request, $serviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt PUT Request mit Circuit Breaker Schutz aus
|
||||
*/
|
||||
public function put(string $url, mixed $body = null, array $headers = [], ?string $serviceName = null): ClientResponse
|
||||
{
|
||||
$request = new ClientRequest('PUT', $url, $headers, $body);
|
||||
|
||||
return $this->request($request, $serviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt DELETE Request mit Circuit Breaker Schutz aus
|
||||
*/
|
||||
public function delete(string $url, array $headers = [], ?string $serviceName = null): ClientResponse
|
||||
{
|
||||
$request = new ClientRequest('DELETE', $url, $headers);
|
||||
|
||||
return $this->request($request, $serviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt Status aller konfigurierten Services zurück
|
||||
*/
|
||||
public function getServicesStatus(): array
|
||||
{
|
||||
$status = [];
|
||||
|
||||
foreach (array_keys($this->serviceConfigs) as $serviceName) {
|
||||
$metrics = $this->circuitBreaker->getMetrics($serviceName);
|
||||
$status[$serviceName] = [
|
||||
'state' => $metrics['state'],
|
||||
'failure_count' => $metrics['failure_count'],
|
||||
'success_count' => $metrics['success_count'],
|
||||
'health_status' => $metrics['state'] === 'closed' ? 'healthy' : 'degraded',
|
||||
'last_failure_time' => $metrics['last_failure_time'],
|
||||
];
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Health Check für einen spezifischen Service
|
||||
*/
|
||||
public function healthCheck(string $serviceName, string $healthCheckUrl): bool
|
||||
{
|
||||
try {
|
||||
$response = $this->get($healthCheckUrl, [], $serviceName);
|
||||
|
||||
return $response->getStatusCode() >= 200 && $response->getStatusCode() < 300;
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt Circuit Breaker für einen Service zurück
|
||||
*/
|
||||
public function resetService(string $serviceName): void
|
||||
{
|
||||
$this->circuitBreaker->reset($serviceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt alle Service Circuit Breaker zurück
|
||||
*/
|
||||
public function resetAll(): void
|
||||
{
|
||||
foreach (array_keys($this->serviceConfigs) as $serviceName) {
|
||||
$this->circuitBreaker->reset($serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert Service-Name aus der URL
|
||||
*/
|
||||
private function extractServiceName(ClientRequest $request): string
|
||||
{
|
||||
$url = $request->getUrl();
|
||||
$parsedUrl = parse_url($url);
|
||||
|
||||
// Service-Name aus Host extrahieren
|
||||
$host = $parsedUrl['host'] ?? 'unknown';
|
||||
|
||||
// Subdomain als Service-Name verwenden falls vorhanden
|
||||
$parts = explode('.', $host);
|
||||
if (count($parts) > 2) {
|
||||
return $parts[0]; // z.B. "api" aus "api.example.com"
|
||||
}
|
||||
|
||||
// Sonst den ganzen Host als Service-Name verwenden
|
||||
return str_replace(['.', '-'], '_', $host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard-Konfiguration für HTTP Services
|
||||
*/
|
||||
private function getDefaultConfig(): CircuitBreakerConfig
|
||||
{
|
||||
return new CircuitBreakerConfig(
|
||||
failureThreshold: 5,
|
||||
recoveryTimeout: Duration::fromSeconds(60),
|
||||
halfOpenMaxAttempts: 3,
|
||||
successThreshold: 2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory für spezifische Services
|
||||
*/
|
||||
public static function withServices(
|
||||
CircuitBreaker $circuitBreaker,
|
||||
HttpClient $httpClient,
|
||||
array $services
|
||||
): self {
|
||||
$configs = [];
|
||||
|
||||
foreach ($services as $serviceName => $config) {
|
||||
if (is_array($config)) {
|
||||
$configs[$serviceName] = new CircuitBreakerConfig(...$config);
|
||||
} elseif ($config instanceof CircuitBreakerConfig) {
|
||||
$configs[$serviceName] = $config;
|
||||
} else {
|
||||
$configs[$serviceName] = CircuitBreakerConfig::forExternalService();
|
||||
}
|
||||
}
|
||||
|
||||
return new self($circuitBreaker, $httpClient, $configs);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user