*/ 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); } }