En production, tout finit par tomber en panne un instant : réseau, base, API partenaire. La résilience, c'est prévoir le coup : réessayer intelligemment, abandonner vite quand un service est clairement en panne (le disjoncteur, comme celui de votre tableau électrique), et ne jamais traiter deux fois le même message.
Stratégies face aux pannes transitoires Confirmé
| Stratégie | Protège contre | Règle |
|---|---|---|
| Timeout | Dépendance qui ne répond plus | Toujours un timeout par tentative et un timeout global |
| Retry | Erreur passagère (503, 408, réseau) | Backoff exponentiel + jitter ; seulement sur opérations idempotentes |
| Circuit breaker | Dépendance durablement en panne | Échouer vite au lieu de saturer threads et connexions |
| Bulkhead / limiteur de concurrence | Une dépendance lente qui consomme toutes les ressources | Plafonner les appels simultanés par dépendance |
| Fallback | Fonction dégradable | Valeur en cache, réponse partielle, message clair |
// Microsoft.Extensions.Http.Resilience (basé sur Polly v8)
builder.Services.AddHttpClient<IPartnerApi, PartnerApi>(c => c.BaseAddress = new(cfg["Partner:Url"]!))
.AddStandardResilienceHandler(o =>
{
o.AttemptTimeout.Timeout = TimeSpan.FromSeconds(2);
o.Retry.MaxRetryAttempts = 3; // backoff exponentiel + jitter par défaut
o.CircuitBreaker.FailureRatio = 0.5; // 50 % d'échecs…
o.CircuitBreaker.MinimumThroughput = 10; // …sur au moins 10 appels
o.CircuitBreaker.BreakDuration = TimeSpan.FromSeconds(15);
o.TotalRequestTimeout.Timeout = TimeSpan.FromSeconds(10);
});
// Ordre du handler standard : limiteur de débit → timeout total → retry → circuit breaker → timeout par tentative
Messaging : livraison au moins une fois Senior
Pub/Sub, RabbitMQ, Service Bus garantissent au moins une livraison : un message peut arriver plusieurs fois, et pas forcément dans l'ordre. Deux patterns rendent cela sûr.
Outbox transactionnelle
Le handler écrit l'agrégat et le message dans la même transaction. Un relais publie ensuite les messages en attente et les marque envoyés. Sans outbox, un crash entre SaveChanges et Publish perd l'événement.
Consommateur idempotent
Le consommateur enregistre l'identifiant de chaque message traité (table inbox, contrainte d'unicité) dans la même transaction que son effet. Un doublon est détecté et ignoré.
Bibliothèques qui implémentent outbox, inbox, retries et sagas : MassTransit (licence commerciale à partir de la v9), Wolverine, NServiceBus (commercial), ou une implémentation maison sur BackgroundService pour un besoin simple. Pour un processus métier long qui traverse plusieurs services, une saga enchaîne les étapes et déclenche des actions compensatoires en cas d'échec.
- Retry sur un POST non idempotent : double paiement, double commande.
- Retries empilés : 3 retries côté client × 3 côté passerelle × 3 côté service = 27 appels sur une dépendance déjà en difficulté.
- Message empoisonné : un message qui échoue toujours bloque la file. Configurer une dead-letter topic et une limite de tentatives.
À retenir
- Timeout partout, retry avec jitter sur l'idempotent, circuit breaker sur chaque dépendance externe.
AddStandardResilienceHandlercouvre la plupart des clients HTTP.- Outbox côté émetteur, idempotence côté consommateur, dead-letter pour le reste.