diff --git a/documentation/user/use-cases/_index.fr.md b/documentation/user/use-cases/_index.fr.md new file mode 100644 index 00000000..126c9da9 --- /dev/null +++ b/documentation/user/use-cases/_index.fr.md @@ -0,0 +1,32 @@ +--- +title: Cas d'usage +weight: 45 +--- + +# Cas d'usage + +Le reste de ce guide est de la référence : une page par fonctionnalité, `await` ici, les signaux +là, Nexus plus loin. Cette section fait l'inverse. Chaque entrée est **une chose entière** — +plusieurs applications, plusieurs mécanismes, un problème qui existe en dehors de Durable — avec +son code dans le dépôt et de quoi la lancer. + +Ce ne sont pas des exercices avancés. Aucune n'est difficile ; elles sont *complètes*. C'est la +seule chose qui les distingue d'un exemple de la section [Écrire un workflow](../workflows/). + +| | | +|---|---| +| [Quatre applications qui s'appellent](nexus-demo/) | trois frameworks, quatre namespaces Temporal, un contrat partagé — et une exécution qui sert une opération pendant qu'elle en appelle une autre | + +Une seconde entrée — un agent IA interruptible, dont la boucle est pilotée depuis du code de +workflow — attend que son prototype se pose. Elle arrivera avec de quoi la lancer : une entrée qui +renvoie vers quelque chose qu'on ne peut pas démarrer n'est pas une entrée. + +## Ce qu'une entrée doit contenir + +Pour que la section reste lisible quand elle grandira, chaque page dit, dans cet ordre : + +1. **le problème**, formulé sans le mot « Durable » ; +2. **ce qui est construit** — les fichiers, où ils sont ; +3. **ce que Durable apporte**, et surtout **ce qu'il n'apporte pas** ; +4. **comment on la lance** ; +5. **ce qui n'est pas prouvé.** Une entrée sans cette partie est une brochure. diff --git a/documentation/user/use-cases/_index.md b/documentation/user/use-cases/_index.md new file mode 100644 index 00000000..3586932f --- /dev/null +++ b/documentation/user/use-cases/_index.md @@ -0,0 +1,32 @@ +--- +title: Use cases +weight: 45 +--- + +# Use cases + +The rest of this guide is reference material: one page per feature — `await` here, signals there, +Nexus further on. This section does the opposite. Each entry is **a whole thing** — several +applications, several mechanisms, a problem that exists outside Durable — with its code in the +repository and a way to run it. + +These are not advanced exercises. None of them is hard; they are *complete*. That is the only +thing separating them from an example in [Writing a workflow](../workflows/). + +| | | +|---|---| +| [Four applications calling each other](nexus-demo/) | three frameworks, four Temporal namespaces, one shared contract — and an execution that serves one operation while calling another | + +A second entry — an interruptible AI agent, its loop driven from workflow code — is waiting for its +prototype to settle. It will land with a way to run it: an entry pointing at something you cannot +start is not an entry. + +## What an entry must contain + +So the section stays readable as it grows, every page says, in this order: + +1. **the problem**, stated without the word "Durable"; +2. **what was built** — the files, and where they live; +3. **what Durable brings**, and above all **what it does not**; +4. **how to run it**; +5. **what is not proven.** An entry without that part is a brochure. diff --git a/documentation/user/use-cases/nexus-demo.fr.md b/documentation/user/use-cases/nexus-demo.fr.md new file mode 100644 index 00000000..b2feab2b --- /dev/null +++ b/documentation/user/use-cases/nexus-demo.fr.md @@ -0,0 +1,105 @@ +--- +title: Quatre applications qui s'appellent +weight: 10 +--- + +# Quatre applications qui s'appellent + +## Le problème + +Une commande traverse quatre systèmes qui n'appartiennent pas à la même équipe : la boutique retient +le stock, le métier facture, la logistique planifie et expédie, l'ERP suit. Chacun a son dépôt, son +framework, son rythme de déploiement. Aucun n'a envie d'importer le code d'un autre. + +La façon habituelle de coudre ça — une API HTTP par service, un client par appelant, un retry par +client, un timeout par retry — marche jusqu'au jour où l'un des quatre est éteint pendant la +transaction. Alors quelqu'un doit décider si on attend, si on rejoue, et ce qu'il advient de ce qui +a déjà été pris. + +## Ce qui est construit + +Quatre applications, quatre namespaces Temporal, trois frameworks. Elles vivent dans le dépôt, sous +[`sylius/`](https://github.com/gplanchat/durable-dev/tree/main/sylius), +[`symfony/`](https://github.com/gplanchat/durable-dev/tree/main/symfony), +[`magento/`](https://github.com/gplanchat/durable-dev/tree/main/magento) et +[`laravel/`](https://github.com/gplanchat/durable-dev/tree/main/laravel). + +| | la boutique | le métier | le banc Magento | la logistique | +|---|---|---|---|---| +| framework | Sylius | Symfony | Mage-OS | Laravel | +| sert | `stock` | `facturation` | — | `livraison` | +| appelle | `facturation` | `stock` | les trois | `stock`, **depuis le workflow qui sert** | +| PHP | 8.3 | 8.3 | 8.2 | 8.2 | + +Les quatre lisent le même paquet de contrats, `src/DurableDemoContracts/`. **Rien d'autre ne circule +entre elles** : pas de client HTTP, pas de SDK partagé, pas de classe d'implémentation. + +## Ce que Durable apporte + +**Appeler ne demande rien.** `WorkflowEnvironment::nexusStub()` lit le contrat par réflexion. +Servir se câble une fois par hôte — et se câble *hors* de Symfony : la logistique enregistre ses +gestionnaires avec deux classes et six lignes de `config/durable.php`, le banc Magento câble en +`di.xml`. La moitié servante de Nexus n'est pas une fonctionnalité du bundle. + +**Les deux formes s'écrivent pareil.** `CommandeWorkflow` appelle `verifier` puis `encaisser` sur le +même stub. La première revient en quelques millisecondes, servie par une méthode ordinaire ; la +seconde prend une quinzaine de secondes, remplie par un workflow d'en face. **Le code de l'appelant +ne distingue pas les deux**, et c'est tout le sujet. + +**L'attente ne tient rien d'ouvert.** Pendant une mise au point, le worker qui devait faire avancer +l'encaissement est resté éteint quatre minutes. L'opération est restée en +`NEXUS_OPERATION_STARTED`, l'appelant n'a rien consommé, et tout s'est terminé normalement quand le +worker est revenu. Aucune connexion, aucun processus, aucune transaction n'attendait. Refait depuis +Magento : 49 secondes, même résultat. + +## Ce qu'il n'apporte pas + +**Pas la compensation.** Aucun des trois contrats n'a d'opération qui rende ce qu'il a pris. La +seule protection est **l'ordre des appels** : `CommandeNexusWorkflow` demande d'abord tout ce qui +peut dire non — vérifier la facture, planifier la tournée, retenir le stock — et n'engage +qu'ensuite. Les deux ordres inverses ont été écrits d'abord et mesurés : une commande en USD +retenait le stock avant de se faire refuser la facture, et une commande de six colis était +**encaissée** avant que la logistique ne refuse de la porter. + +**Pas l'idempotence.** Une tâche Nexus est redélivrée ; c'est le gestionnaire qui doit tenir. Celui +de `stock` écrit son verdict dans `app_durable_stock_reservation`, clé par identifiant de commande — +rejouer la même commande rend le même verdict et ne retient pas de stock une seconde fois. Ça a +été écrit à la main, Durable ne l'a pas fourni. + +## Comment on la lance + +```bash +bin/demo-nexus # d'abord : les namespaces et les endpoints Nexus +demo/lancer.sh # ensuite : les huit workers +demo/lancer.sh --etat # dit qui tourne +demo/lancer.sh --arreter # les arrête +``` + +`bin/demo-nexus` passe en premier, et il n'est pas facultatif : les workers se connectent à des +endpoints qui n'existent pas tant qu'il ne les a pas créés. Les deux scripts impriment, une fois +finis, les commandes d'appel avec les bonnes valeurs. + +L'ordre de démarrage n'a pas d'importance : un worker en retard fait attendre, il ne fait pas +échouer. + +Deux prérequis qui ne se devinent pas, et que +[`demo/README.md`](https://github.com/gplanchat/durable-dev/blob/main/demo/README.md) détaille : + +- **un serveur Temporal dont les API Nexus sont actives.** `temporal server start-dev` convient ; + `temporalio/auto-setup:1.25.2` répond `Nexus APIs are disabled` à la création d'endpoint ; +- **deux binaires PHP.** 8.3 pour les deux maquettes Symfony, 8.2 pour Magento et Laravel — c'est + mesuré, pas frileux : sur le poste de référence aucune version unique n'a l'intersection des + extensions exigées. + +## Ce qui n'est pas prouvé + +- **La montée en charge.** Quatre applications sur un poste, un serveur `start-dev`, une commande à + la fois. Rien ici ne dit ce que fait une file Nexus sous charge réelle. +- **La reprise après un échec du gestionnaire servant.** Ce qui a été mesuré, c'est un worker + *éteint* — pas un gestionnaire qui lève au milieu de son travail. +- **La sécurité.** Les quatre namespaces sont sur le même serveur sans mTLS ni autorisation. Le + cloisonnement inter-équipes, qui est la moitié de l'argument Nexus, n'est pas démontré. + +Le détail de ce que chaque maquette a ajouté, maquette par maquette, est dans +[`demo/README.md`](https://github.com/gplanchat/durable-dev/blob/main/demo/README.md). La mécanique +Nexus elle-même est décrite dans [Opérations Nexus](../../nexus/). diff --git a/documentation/user/use-cases/nexus-demo.md b/documentation/user/use-cases/nexus-demo.md new file mode 100644 index 00000000..eb12cf3d --- /dev/null +++ b/documentation/user/use-cases/nexus-demo.md @@ -0,0 +1,101 @@ +--- +title: Four applications calling each other +weight: 10 +--- + +# Four applications calling each other + +## The problem + +An order crosses four systems owned by four different teams: the shop holds stock, the business +side invoices, logistics plans and ships, the ERP follows. Each has its own repository, framework +and release cadence. None of them wants to import another one's code. + +The usual way to stitch that together — one HTTP API per service, one client per caller, one retry +per client, one timeout per retry — works until one of the four is down mid-transaction. Then +someone has to decide whether to wait, whether to replay, and what happens to what was already +taken. + +## What was built + +Four applications, four Temporal namespaces, three frameworks. They live in the repository under +[`sylius/`](https://github.com/gplanchat/durable-dev/tree/main/sylius), +[`symfony/`](https://github.com/gplanchat/durable-dev/tree/main/symfony), +[`magento/`](https://github.com/gplanchat/durable-dev/tree/main/magento) and +[`laravel/`](https://github.com/gplanchat/durable-dev/tree/main/laravel). + +| | the shop | the business side | the Magento bench | logistics | +|---|---|---|---|---| +| framework | Sylius | Symfony | Mage-OS | Laravel | +| serves | `stock` | `facturation` | — | `livraison` | +| calls | `facturation` | `stock` | all three | `stock`, **from the workflow that serves** | +| PHP | 8.3 | 8.3 | 8.2 | 8.2 | + +All four read the same contract package, `src/DurableDemoContracts/`. **Nothing else travels between +them**: no HTTP client, no shared SDK, no implementation class. + +## What Durable brings + +**Calling requires nothing.** `WorkflowEnvironment::nexusStub()` reads the contract by reflection. +Serving is wired once per host — and it wires up *outside* Symfony: logistics registers its handlers +with two classes and six lines of `config/durable.php`, the Magento bench wires in `di.xml`. The +serving half of Nexus is not a bundle feature. + +**Both shapes are written the same way.** `CommandeWorkflow` calls `verifier`, then `encaisser`, on +the same stub. The first returns in milliseconds, served by an ordinary method; the second takes +about fifteen seconds, fulfilled by a workflow on the other side. **The caller's code does not tell +them apart**, and that is the whole point. + +**Waiting holds nothing open.** During a debugging session the worker that was to advance the +payment stayed down for four minutes. The operation stayed in `NEXUS_OPERATION_STARTED`, the caller +consumed nothing, and everything completed normally when the worker came back. No connection, no +process, no transaction was waiting. Repeated from Magento: 49 seconds, same result. + +## What it does not bring + +**Not compensation.** None of the three contracts has an operation that gives back what it took. The +only protection is **call ordering**: `CommandeNexusWorkflow` first asks everything that can say no +— check the invoice, plan the round, hold the stock — and only then commits. Both reverse orders +were written first, and measured: a USD order held stock before being refused an invoice, and a +six-parcel order was **charged** before logistics refused to carry it. + +**Not idempotency.** A Nexus task gets redelivered; the handler has to hold. The `stock` handler +writes its verdict to `app_durable_stock_reservation`, keyed by order id — replaying the same order +returns the same verdict and does not hold stock twice. That was written by hand; Durable did not +provide it. + +## How to run it + +```bash +bin/demo-nexus # first: the namespaces and the Nexus endpoints +demo/lancer.sh # then: the eight workers +demo/lancer.sh --etat # report who is running +demo/lancer.sh --arreter # stop them +``` + +`bin/demo-nexus` comes first and is not optional: the workers connect to endpoints that do not +exist until it has created them. Both scripts print the call commands with the right values once +they are done. + +Start order does not matter: a late worker makes things wait, it does not make them fail. + +Two prerequisites you would not guess, detailed in +[`demo/README.md`](https://github.com/gplanchat/durable-dev/blob/main/demo/README.md): + +- **a Temporal server with the Nexus APIs enabled.** `temporal server start-dev` will do; + `temporalio/auto-setup:1.25.2` answers `Nexus APIs are disabled` on endpoint creation; +- **two PHP binaries.** 8.3 for the two Symfony apps, 8.2 for Magento and Laravel — measured, not + timid: on the reference machine no single version has the intersection of required extensions. + +## What is not proven + +- **Scale.** Four applications on one machine, a `start-dev` server, one order at a time. Nothing + here says what a Nexus queue does under real load. +- **Recovery from a failing serving handler.** What was measured is a worker that was *down* — not a + handler that throws halfway through its work. +- **Security.** The four namespaces sit on the same server with no mTLS and no authorization. + Cross-team isolation, which is half the Nexus argument, is not demonstrated. + +What each app added, one by one, is in +[`demo/README.md`](https://github.com/gplanchat/durable-dev/blob/main/demo/README.md). The Nexus +mechanics themselves are described in [Nexus operations](../../nexus/).