La spec MicroProfile Fault Tolerance 4.1 définit six politiques composables. Cette page expose le vocabulaire (Retry, Timeout, CircuitBreaker, Bulkhead, Fallback, Asynchronous), l’ordre canonique de composition imposé par §2.5, et la distinction conceptuelle entre les politiques de récupération et celles de protection.

Les six politiques

Annotation Famille Rôle

@Retry

Récupération

Ré-essais transparents en cas d’échec transitoire — délai, jitter, retryOn / abortOn.

@Timeout

Protection

Borne maximale du temps d’exécution d’une tentative.

@CircuitBreaker

Protection

Coupe les appels vers un service en panne — fenêtre glissante, transitions CLOSED/OPEN/HALF_OPEN.

@Bulkhead

Protection

Isole un point d’appel — sémaphore (sync) ou file bornée (async).

@Fallback

Récupération

Fournit un résultat alternatif quand toutes les autres politiques ont échoué.

@Asynchronous

Exécution

Délègue l’invocation à un virtual thread ; la méthode renvoie CompletionStage ou Future.

Distinction clé : récupération (@Retry, @Fallback) cherche à obtenir un résultat malgré l’échec ; protection (@Timeout, @CircuitBreaker, @Bulkhead) cherche à limiter le dommage subi par le système appelant.

Ordre canonique de composition

La spec MP FT 4.1 §2.5 impose un ordre de wrapping unique, de l’extérieur vers l’intérieur :

Diagram

@Asynchronous se positionne au-dessus de cette chaîne quand il est présent : il bascule l’exécution sur un virtual thread, puis la chaîne @Fallback → …​ → méthode est invoquée dans ce thread.

Conséquence concrète : la composition est non-commutative. Un @Retry autour d’un @Timeout (ce que fait la chaîne) réessaie chaque tentative timée. À l’inverse, un @Timeout autour d’un @Retry (qu’on ne peut pas exprimer en MP FT) timerait la séquence entière de retries. La spec choisit la première forme — chaque tentative a son propre budget temps.

États du circuit breaker

@CircuitBreaker est un automate à trois états :

Diagram
État Sémantique

CLOSED

Les appels passent. La fenêtre requestVolumeThreshold enregistre succès et échecs. Si le ratio d’échec dépasse failureRatio, transition vers OPEN.

OPEN

Les appels sont rejetés immédiatement par un CircuitBreakerOpenException. Au bout de delay, transition vers HALF_OPEN.

HALF_OPEN

Une tentative de sonde est admise. Si successThreshold tentatives consécutives réussissent, transition vers CLOSED. Tout échec renvoie en OPEN.

Métaphore Heisenberg : l’état OPEN est l'effondrement du paquet d’ondes — l’observation a forcé le système dans un état discret, et il faut attendre delay avant que la superposition (sonde HALF_OPEN) ne redevienne possible.

@Fallback vs @CircuitBreaker

Confusion fréquente : les deux interviennent en cas d’échec, mais leur rôle est orthogonal.

Aspect @Fallback @CircuitBreaker

Famille

Récupération

Protection

Question posée

« Que renvoyer si l’appel a échoué ? »

« Faut-il encore essayer d’appeler ? »

Effet sur l’appelant

Voit un résultat (fallback) au lieu d’une exception

Voit CircuitBreakerOpenException (sauf si @Fallback l’intercepte)

Effet sur le système distant

Aucun — l’appel a déjà eu lieu

Réduit la pression — les appels suivants sont court-circuités

Les deux se combinent naturellement : @CircuitBreaker protège, @Fallback récupère le CircuitBreakerOpenException levé.

Sémantique de @Asynchronous

@Asynchronous exige que la méthode retourne CompletionStage<T> ou Future<T>. L’appel est délégué à un virtual thread. Pour CompletionStage :

  1. L’intercepteur retourne immédiatement un CompletableFuture<T> vide.

  2. Un virtual thread est créé (Thread.ofVirtual().start(…​)).

  3. La chaîne @Fallback → @CircuitBreaker → @Bulkhead → @Timeout → @Retry → méthode s’exécute sur ce virtual thread.

  4. Quand le résultat est connu, CompletableFuture.complete(value) est appelé.

Pour Future<T>, le contrat est plus restrictif : Future.cancel(true) interrompt le virtual thread (best-effort), Future.get(timeout) bloque le thread appelant jusqu’à timeout.

La méthode annotée @Asynchronous ne doit pas être appelée directement depuis un autre virtual thread de la même chaîne — la spec §6.4 interdit l’auto-référence pour éviter les deadlocks. L’intercepteur Heisenberg détecte le cas et lève FaultToleranceDefinitionException au démarrage de l’application.

Sémantique de @Bulkhead

@Bulkhead(value=N) borne le nombre d’invocations concurrentes :

  • Mode synchrone (sans @Asynchronous) : un Semaphore à N permis ; un appel au-delà lève BulkheadException immédiatement.

  • Mode asynchrone (avec @Asynchronous) : un Semaphore à N permis + une file de waitingTaskQueue tâches en attente ; au-delà, BulkheadException.

Configuration via MicroProfile Config

La spec MP FT 4.1 §12 définit un schéma de clés fault-tolerance/…​ lu via MicroProfile Config — fourni par Ravel.

Clé Effet

fault-tolerance/<Annotation>/<param>

Valeur globale, prioritaire sur la valeur par défaut de l’annotation.

fault-tolerance/<Classe>/<Annotation>/<param>

Override au niveau classe.

fault-tolerance/<Classe>/<méthode>/<Annotation>/<param>

Override au niveau méthode — prioritaire absolu.

fault-tolerance/MP_Fault_Tolerance_NonFallback_Enabled

Booléen. false désactive tout sauf @Fallback.

fault-tolerance/MP_Fault_Tolerance_Metrics_Enabled

Booléen. false désactive l’émission de métriques §9 / §10.

Voir Référence pour la liste exhaustive et les variantes par paramètre.

Pour aller plus loin

  • Cas d’usage — chaînes typiques (@Retry + @Timeout + @CircuitBreaker + @Fallback), FallbackHandler, @Asynchronous.

  • Fonctionnement interne — pipeline d’invocation, moteurs purs, virtual threads, StateRegistry.

  • Référence — annotations, paramètres, clés MP Config.