MVAESTECH
Guide .NET · 13 · Production

Publié le · par Mathieu Vaes

Résilience & messaging

Dans un système distribué, le réseau échoue, les dépendances ralentissent et les messages arrivent deux fois. L'architecture doit l'accepter comme un fonctionnement normal.

Chapitres du guide
En clair

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égieProtège contreRègle
TimeoutDépendance qui ne répond plusToujours un timeout par tentative et un timeout global
RetryErreur passagère (503, 408, réseau)Backoff exponentiel + jitter ; seulement sur opérations idempotentes
Circuit breakerDépendance durablement en panneÉchouer vite au lieu de saturer threads et connexions
Bulkhead / limiteur de concurrenceUne dépendance lente qui consomme toutes les ressourcesPlafonner les appels simultanés par dépendance
FallbackFonction dégradableValeur 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.

HandlerSubmitOrder une seule transaction SQL table Orders table Outbox RelaisBackgroundService Pub/Sub Consom.idempotent L'état et le message sont écrits ensemble : jamais de commande validée sans événement publié, ni l'inverse

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.

Piège
  • 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.
  • AddStandardResilienceHandler couvre la plupart des clients HTTP.
  • Outbox côté émetteur, idempotence côté consommateur, dead-letter pour le reste.

Un projet .NET à cadrer ou à reprendre ?

API REST documentées et testées, applications ASP.NET MVC (y compris VB.NET), migration de services anciens vers une architecture claire : parlons de votre code.