Appeler un service extérieur
Votre module appelle un transporteur, un fournisseur de taux, une API de facturation. Ça marche pendant six mois. Puis un jour, le service d’en face ne répond plus : il ne refuse pas, il ne répond pas.
Sans garde-fou, voilà ce qui se passe. Chaque appel attend. Les travailleurs qui traitent la file attendent tous. Les requêtes web qui déclenchent l’appel attendent aussi. Au bout de quelques minutes, votre instance ne répond plus non plus, pour une panne qui n’est pas la vôtre.
C’est l’interdiction n°18 du projet, et elle est courte : tout appel sortant a un délai et un disjoncteur.
Le délai
from kewos.resilience import DEFAULT_HTTP_TIMEOUT_SECONDS
response = httpx.get(url, timeout=DEFAULT_HTTP_TIMEOUT_SECONDS)Les valeurs du noyau, pour ne pas les réinventer par appel :
| Ce qu’on attend | Délai |
|---|---|
| un appel HTTP sortant | 30 s |
| une requête à la base | 10 s |
| un travail de fond | 300 s |
Un client HTTP sans timeout attend indéfiniment. C’est le défaut de la plupart des
bibliothèques, et c’est le premier maillon de la panne décrite plus haut.
Le disjoncteur
Le délai borne un appel. Le disjoncteur arrête d’appeler un service qu’on sait tombé.
from kewos.resilience import CircuitBreaker, CircuitOpenError
# Un par dépendance, construit UNE fois au démarrage du module : il est# stateful, c'est tout son intérêt.TARIFS = CircuitBreaker(name="fournisseur.tarifs", failure_threshold=3, timeout_seconds=30)
def tarif(colis): try: return TARIFS.call(appel_http, colis) except CircuitOpenError: return None # le service est connu tombé : on n'y va même pasExécuté, avec un service qui échoue à chaque fois :
appel 1: parti, échoué -> le service ne répond pas (état closed, 1 échec)appel 2: parti, échoué -> le service ne répond pas (état closed, 2 échecs)appel 3: parti, échoué -> le service ne répond pas (état open, 3 échecs)appel 4: refusé AVANT de sortir -> CircuitOpenError: circuit breaker 'fournisseur.tarifs' is openappel 5: refusé AVANT de sortir -> CircuitOpenError: circuit breaker 'fournisseur.tarifs' is openappels réellement sortis: 3Trois appels sont partis, deux ont été refusés sans sortir. C’est la différence entre une dépendance lente et une instance lente : au quatrième appel, votre code répond immédiatement.
Et il se referme tout seul :
après 2 échecs, état: open31 s plus tard, un appel sonde passe: réponseétat après la sonde réussie: closedPassé le délai de récupération, le disjoncteur laisse passer une sonde. Si elle réussit, il se referme ; si elle échoue, il se rouvre pour un tour. Personne n’a rien à réarmer à la main.
Le réessai, et quand il ne faut pas
from kewos.resilience import retry_with_backoff
resultat = retry_with_backoff(appel, attempts=3, retry_on=(TimeoutError,))retry_with_backoff -> réponse en 3 tentativesLe repli est exponentiel avec une part d’aléatoire, pour ne pas synchroniser tous vos travailleurs sur la même seconde.
Ce que ça n’excuse pas
Un disjoncteur ouvert n’est pas une panne silencieuse. L’utilisateur doit voir que le service est partiel (interdiction n°25), et le module doit décider quoi faire sans le service : un tarif estimé, une file d’attente, un écran qui le dit. Le disjoncteur vous rend la main vite pour que vous ayez ce choix ; il ne le fait pas à votre place.
Et les appels que vous ne passez pas vous-même
Un appel à un modèle d’IA passe par kewos/ai, un envoi de courriel par
la file, une lecture de document par kewos/ocr. Ces façades portent déjà
leur délai et leur disjoncteur : c’est une bonne raison de plus de passer par elles plutôt que
d’appeler un fournisseur directement.