# Circuit Breakers — Documentation

**Date**: 2025-01-27  
**Status**: ✅ **IMPLEMENTED** - MOD-P2-007

---

## Vue d'ensemble

Les circuit breakers protègent l'application contre les dépendances externes lentes ou indisponibles en interrompant automatiquement les appels après un seuil d'échecs.

### Implémentation

- **Bibliothèque**: `github.com/sony/gobreaker`
- **Wrapper**: `internal/services/circuit_breaker.go`
- **Métriques**: `internal/metrics/circuit_breaker.go`

---

## Configuration

### Paramètres par défaut

```go
MaxRequests: 3          // Requêtes simultanées max
Interval: 60s          // Réinitialisation des compteurs
Timeout: 30s           // Délai avant half-open
ReadyToTrip: 5 échecs  // Seuil pour ouvrir le circuit
```

### États du Circuit Breaker

1. **Closed** (Fermé): État normal, toutes les requêtes passent
2. **Open** (Ouvert): Circuit ouvert après 5 échecs consécutifs, requêtes rejetées
3. **Half-Open** (Demi-ouvert): Après 30s, permet quelques requêtes de test

---

## Utilisation

### Création d'un client avec circuit breaker

```go
import (
    "veza-backend-api/internal/services"
    "go.uber.org/zap"
)

logger := zap.NewNop()
httpClient := &http.Client{Timeout: 10 * time.Second}
cbClient := services.NewCircuitBreakerHTTPClient(
    httpClient,
    "my-service",  // Nom du circuit breaker (pour métriques)
    logger,
)
```

### Exécution d'une requête

```go
req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
resp, err := cbClient.Do(req)
if err != nil {
    // Gérer l'erreur (circuit ouvert, timeout, 5xx, etc.)
    return err
}
defer resp.Body.Close()
```

### Avec contexte (timeout/cancellation)

```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
resp, err := cbClient.DoWithContext(ctx, req)
```

---

## Services Intégrés

### 1. Stream Service

**Fichier**: `internal/services/stream_service.go`

```go
circuitBreaker: NewCircuitBreakerHTTPClient(
    httpClient,
    "stream-service",
    logger,
)
```

**Utilisation**: Appels HTTP vers le serveur de streaming pour transcodage.

### 2. OAuth Service

**Fichier**: `internal/services/oauth_service.go`

```go
circuitBreaker: NewCircuitBreakerHTTPClient(
    httpClient,
    "oauth-service",
    logger,
)
```

**Utilisation**: Appels HTTP vers les providers OAuth (Google, GitHub, Discord).

---

## Métriques Prometheus

Les métriques suivantes sont exposées automatiquement:

### `veza_circuit_breaker_state`
**Type**: Gauge  
**Labels**: `circuit_breaker_name`  
**Valeurs**: 
- `0` = Closed
- `1` = Half-Open
- `2` = Open

**Exemple**:
```
veza_circuit_breaker_state{circuit_breaker_name="stream-service"} 0
```

### `veza_circuit_breaker_requests_total`
**Type**: Counter  
**Labels**: `circuit_breaker_name`, `result` (success|failure|rejected)

**Exemple**:
```
veza_circuit_breaker_requests_total{circuit_breaker_name="stream-service",result="success"} 150
veza_circuit_breaker_requests_total{circuit_breaker_name="stream-service",result="failure"} 5
veza_circuit_breaker_requests_total{circuit_breaker_name="stream-service",result="rejected"} 2
```

### `veza_circuit_breaker_failures_total`
**Type**: Counter  
**Labels**: `circuit_breaker_name`

**Exemple**:
```
veza_circuit_breaker_failures_total{circuit_breaker_name="stream-service"} 5
```

### `veza_circuit_breaker_consecutive_failures`
**Type**: Gauge  
**Labels**: `circuit_breaker_name`

**Exemple**:
```
veza_circuit_breaker_consecutive_failures{circuit_breaker_name="stream-service"} 3
```

---

## Comportement sur Erreurs

### Codes HTTP 5xx

Les codes HTTP 5xx (500, 502, 503, etc.) sont considérés comme des **échecs** et comptent pour le circuit breaker:

```go
if resp.StatusCode >= 500 {
    resp.Body.Close()
    return nil, fmt.Errorf("server error: %d", resp.StatusCode)
}
```

### Circuit Ouvert

Quand le circuit est ouvert, les requêtes sont **rejetées immédiatement** sans appel HTTP:

```go
if err == gobreaker.ErrOpenState {
    return nil, fmt.Errorf("circuit breaker is open: service unavailable")
}
```

---

## Tests

### Tests unitaires

```bash
go test ./internal/services -v -run TestCircuitBreaker
```

**Tests inclus**:
- Création du client
- Requêtes réussies
- Gestion des erreurs 5xx
- Ouverture du circuit après seuil
- Rejet de requêtes quand circuit ouvert
- Support du contexte (timeout/cancellation)

### Test d'intégration (mock server)

Un test simule un serveur qui retourne 5xx pour déclencher l'ouverture du circuit:

```bash
go test ./internal/services -v -run TestCircuitBreakerHTTPClient_Do_ServerError
```

---

## Variables d'Environnement

Aucune variable d'environnement requise. La configuration est codée en dur dans le wrapper pour simplifier.

**Pour personnaliser** (si nécessaire):
- Modifier `internal/services/circuit_breaker.go`
- Ajuster `MaxRequests`, `Interval`, `Timeout`, `ReadyToTrip`

---

## Monitoring et Alertes

### Alertes recommandées

1. **Circuit ouvert trop souvent**:
   ```
   veza_circuit_breaker_state{circuit_breaker_name="stream-service"} == 2
   ```

2. **Taux d'échec élevé**:
   ```
   rate(veza_circuit_breaker_requests_total{result="failure"}[5m]) > 0.1
   ```

3. **Échecs consécutifs**:
   ```
   veza_circuit_breaker_consecutive_failures > 3
   ```

---

## Dépannage

### Circuit reste ouvert

**Cause**: Service externe toujours en erreur  
**Solution**: Vérifier la santé du service externe, attendre 30s (Timeout) pour half-open

### Trop de rejets

**Cause**: Seuil trop bas (5 échecs)  
**Solution**: Augmenter `ReadyToTrip` dans `circuit_breaker.go`

### Métriques manquantes

**Cause**: Métriques non initialisées  
**Solution**: Vérifier que `internal/metrics/circuit_breaker.go` est importé

---

## Références

- [gobreaker Documentation](https://github.com/sony/gobreaker)
- [Circuit Breaker Pattern](https://martinfowler.com/bliki/CircuitBreaker.html)
- [Prometheus Metrics](https://prometheus.io/docs/concepts/metric_types/)

---

**Dernière mise à jour**: 2025-01-27  
**Maintenu par**: Veza Backend Team
