# Orchestration à l'intérieur, chorégraphie entre les deux : combiner Temporal et Kafka

> Un client qui change de banque, c'est une seule décision vu de l'extérieur, et des jours de travail long, dépendant de tiers, vu de l'intérieur. Comment child workflows, signals, updates, Nexus et Kafka trouvent chacun leur place, et où l'orchestration doit s'arrêter.

Source: https://www.bluewise.fr/fr/temporal-child-workflows-bank-switch  
Author: Marc Morel  
Published: 2026-06-22  
Category: Architecture

Un client qui change de banque prend une seule décision. La banque hérite de jours de travail : rediriger chaque prélèvement récurrent, transférer les soldes, clôturer les comptes dans le bon ordre, et tenir le client informé pendant que créanciers et banques contreparties répondent à leur propre rythme.

C'est le cas d'école de l'exécution durable. Il montre aussi pourquoi orchestration et chorégraphie ne sont pas des styles rivaux entre lesquels choisir, mais deux couches d'une même conception : l'une à l'intérieur de chaque processus, l'autre entre eux.

## Le mandat est le parent

Le workflow parent porte le mandat de mobilité bancaire : le client, l'échéance réglementaire, l'état final visé. Son workflow ID est la clé métier, `switch-{mandateId}`. Ce seul choix apporte l'idempotence gratuitement : quel que soit ce qui déclenche deux fois le changement de banque, Temporal refuse le second démarrage.

Le parent encode l'ordre qui compte. Les prélèvements sont déplacés d'abord, les soldes transférés ensuite, les comptes clôturés en dernier. Clôturer un compte avant qu'un prélèvement en attente ait été redirigé, c'est comme ça qu'un client se retrouve avec un loyer rejeté. La séquence vit dans le code, pas dans un runbook. Quand une étape échoue définitivement, le parent compense en sens inverse : un transfert de solde rejeté par la banque destinataire est recrédité avant que quoi que ce soit ne soit clôturé.

## Pourquoi des children, pas des activities

Chaque migration de prélèvement et chaque clôture de compte s'exécute comme un child workflow, `switch-{mandateId}/debit-{ref}`, pour trois raisons.

D'abord, l'historique. Chaque événement atterrit dans l'historique du workflow, et l'historique est borné. Un client avec quarante prélèvements, chacun attendant des retries et des accusés de réception, pousserait un workflow unique vers ses limites. Les children partitionnent l'historique par objet métier.

Ensuite, l'isolation des échecs. Un créancier qui ne répond jamais fait attendre un child, pas tout le mandat. Le parent voit un résultat explicite et typé par prélèvement et décide : escalader, notifier le client, ou continuer sans lui.

Enfin, le versioning. Les workflows de longue durée rejouent leur code d'origine. Découper par objet métier signifie qu'une modification de la logique de prélèvement n'affecte que les children démarrés après le déploiement, tandis que les children en cours se terminent sur la version avec laquelle ils ont commencé.

La parent close policy est un choix délibéré. `REQUEST_CANCEL` permet à un client qui retire son mandat de déclencher un dénouement propre : chaque child reçoit l'annulation et exécute sa propre compensation au lieu d'être tué en plein transfert.

## Attendre, correctement

L'essentiel d'un changement de banque consiste à attendre. Les timers durables expriment directement les échéances : attendre le créancier, mais pas au-delà du délai réglementaire, puis escalader.

Les réponses externes arrivent de deux façons. L'accusé de réception d'une banque contrepartie est un fait auquel le workflow réagit : un **signal**, fire-and-forget. Un client qui confirme la date de clôture attend une réponse en retour : un **update**, validé par le workflow et renvoyant un résultat de façon synchrone à l'API appelante. Utiliser des signals pour tout oblige le frontend à faire du polling ; utiliser des updates pour tout couple les partenaires à votre latence.

Après la clôture, un petit workflow continue de surveiller : des prélèvements arrivent encore sur l'ancien compte pendant des mois. Une sentinelle les réachemine et utilise périodiquement continue-as-new pour garder un historique court.

## Nexus : franchir les frontières entre équipes

Les virements appartiennent à l'équipe paiements, avec son propre namespace, ses workers et sa cadence de release. Le workflow de changement de banque ne devrait pas connaître leurs task queues ni leurs workflow IDs.

C'est toujours de l'orchestration, simplement à travers une frontière d'équipe : le mandat est responsable du résultat du virement et doit y réagir, donc un événement lancé par-dessus le mur ne suffirait pas. Nexus donne à cette frontière un contrat typé. Les paiements exposent un service avec une opération `initiateTransfer` derrière un endpoint Nexus. Le workflow de changement de banque l'appelle comme n'importe quelle opération ; le handler démarre un workflow dans le namespace paiements ; le résultat, ou l'annulation, repasse la frontière en sens inverse. Chaque équipe garde sa responsabilité opérationnelle, et c'est le contrat qu'elles versionnent. L'idempotence franchit aussi la frontière : la requête porte une clé dérivée du workflow ID du child, donc un appel rejoué ne devient jamais un second virement.

## Kafka : des faits en entrée, des faits en sortie

Temporal porte l'état du processus ; Kafka transporte des faits. Demander à Kafka d'orchestrer, c'est là que la plupart des sagas événementielles déraillent : topics de retry, dead-letter queues et état dispersé entre consommateurs deviennent un workflow implicite que personne ne sait lire.

**En entrée**, un consommateur transforme chaque événement pertinent en signal-with-start sur le workflow ID déterministe. Kafka livre au moins une fois, donc cet appel doit être idempotent, et l'offset n'est commité qu'une fois que Temporal l'a accepté. Partitionner par mandate ID préserve l'ordre là où il compte.

**En sortie**, un workflow ne publie jamais directement : cela casserait le déterminisme. La publication est une activity, et les activities font des retries, donc des doublons arriveront. Le producteur idempotent de Kafka ne couvre pas une activity rejouée. Chaque événement porte donc un ID dérivé du workflow ID et de l'étape, et les consommateurs dédupliquent dessus.

## Deux couches, une conception

Les deux styles se rencontrent en trois charnières, chacune étant un passage de relais délibéré.

**La chorégraphie démarre l'orchestration.** La demande de changement de banque est un fait publié par le domaine d'onboarding. Un consommateur la transforme en signal-with-start, et à partir de ce moment un workflow porte le mandat.

**L'orchestration alimente la chorégraphie.** Quand le mandat se termine, il publie `MandateCompleted`. Le reporting, le marketing et la plateforme data réagissent sans que le mandat sache qu'ils existent.

**L'orchestration peut franchir une frontière** quand une partie est responsable du résultat, comme avec Nexus plus haut. Sinon, un fait suffit.

Si un processus a un responsable, une échéance et un état final, orchestrez-le. S'il s'agit d'un fait qui peut intéresser d'autres, publiez-le et laissez-les chorégraphier. Chaque processus orchestré est un nœud dans une chorégraphie plus large, et chaque nœud est lisible isolément.

## Ce qui change

- **Pour le client :** une décision, puis une progression visible. Un créancier silencieux devient une escalade avec une échéance, pas un paiement rejeté sur le relevé du mois suivant.
- **Pour les équipes d'ingénierie :** le processus vit dans du code qu'un nouvel arrivant peut lire, et chaque équipe est responsable de son côté d'un contrat : des opérations typées via Nexus, des faits sur Kafka pour tous les autres.
- **Pour les opérations et la conformité :** chaque mandat a un état que l'on peut interroger, un échec dont on peut reprendre, et une piste d'audit qui est l'exécution elle-même, pas un log reconstitué après coup.

Un changement de banque, c'était autrefois un batch nocturne et des excuses. Construit de cette façon, c'est quelque chose que l'on peut observer, reprendre, et expliquer ligne par ligne à un régulateur.
