[{"data":1,"prerenderedAt":2120},["ShallowReactive",2],{"tag-data-microservices":3},[4],{"id":5,"title":6,"alt":7,"authors":8,"body":15,"date":2092,"description":2093,"extension":2094,"image":2095,"meta":2096,"navigation":319,"ogImage":2095,"path":2097,"published":319,"reviewers":2098,"seo":2113,"stem":2114,"tags":2115,"__hash__":2119},"blogs\u002Fblogs\u002F2026-10-12-cartographier-son-systeme-dinformation-sans-tout-redeployer-la-methode-des-tests-de-maillon\u002Findex.md","Cartographier son système d'information sans tout redéployer : la méthode des tests de maillon","Représentation d’un process de commande dans une boulangerie et des interactions sous-jacentes entre les services",[9],{"id":10,"name":11,"image":12,"linkedin":13,"x":14},"02c620f8-3576-4943-b5cf-6117f99220a2","Edouard Cattez",".\u002Fassets\u002Fauthor-edouard-cattez.webp","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fedouard-cattez-865794133\u002F","https:\u002F\u002Fx.com\u002Fecattez",{"type":16,"value":17,"toc":2074},"minimark",[18,22,30,33,40,46,52,57,60,98,101,104,193,196,201,204,207,213,290,294,300,303,356,359,494,505,509,512,535,538,542,553,556,656,659,848,851,854,857,871,874,882,885,950,962,969,973,976,1023,1026,1030,1033,1048,1062,1065,1069,1079,1169,1187,1190,1194,1211,1214,1264,1267,1380,1383,1413,1416,1422,1779,1782,1786,1800,1811,1823,1897,1911,2036,2048,2051,2055,2058,2061,2064,2067,2070],[19,20,21],"p",{},"Il est huit heures, un samedi matin. Une cliente commande un gâteau d’anniversaire personnalisé sur le site de sa boulangerie. Elle choisit la recette, indique le nombre de parts, joint une image d’inspiration et règle sa commande. Quelques secondes plus tard, elle reçoit une confirmation : son gâteau sera prêt à 16 heures.",[19,23,24,25,29],{},"Pour elle, une seule action vient d’avoir lieu : ",[26,27,28],"strong",{},"commander un gâteau",".",[19,31,32],{},"Dans le système d’information de la boulangerie, l’histoire est plus longue. Le service de commande a enregistré la demande, vérifié les ingrédients auprès du service de stock, sollicité un prestataire de paiement, consulté l’image déposée dans un bucket et publié un événement. Le service de production peut maintenant planifier la préparation, puis le service de notification peut prévenir la cliente.",[19,34,35,36,39],{},"Chaque équipe connaît bien son atelier. ",[26,37,38],{},"Mais si nous demandons ce qui s’est réellement passé entre le clic de la cliente et la confirmation de sa commande","**, la réponse se trouve dispersée entre plusieurs services, bases de données, messages et dépendances externes**.",[19,41,42,43],{},"C’est précisément le problème auquel nous avons voulu répondre avec les tests de maillon : ",[26,44,45],{},"conserver une emprunte des tests pour chaque service tout en rendant progressivement lisible le parcours global.",[47,48,49],"blockquote",{},[19,50,51],{},"Une empreinte est une cartographie comportementale construite à partir des chemins réellement traversés par les scénarios. Elle n’inventorie pas tout le code possible : elle raconte ce que les tests ont effectivement observé.",[53,54,56],"h2",{"id":55},"une-boulangerie-devenue-système-distribué","Une boulangerie devenue système distribué",[19,58,59],{},"Notre boulangerie a grandi. Le cahier posé derrière le comptoir a laissé place à plusieurs applications spécialisées :",[61,62,63,71,77,83,89,92,95],"ul",{},[64,65,66,70],"li",{},[67,68,69],"code",{},"OrderService"," prend les commandes et conserve leur état ;",[64,72,73,76],{},[67,74,75],{},"StockService"," réserve la farine, les œufs, le chocolat et les autres ingrédients ;",[64,78,79,82],{},[67,80,81],{},"ProductionService"," organise le travail du laboratoire ;",[64,84,85,88],{},[67,86,87],{},"NotificationService"," informe les clients ;",[64,90,91],{},"un prestataire externe traite les paiements ;",[64,93,94],{},"un bucket conserve les images et documents liés aux créations personnalisées ;",[64,96,97],{},"un bus transporte les événements entre les ateliers.",[19,99,100],{},"Ce découpage permet à chaque équipe de faire évoluer son domaine. Il crée aussi des interdépendances. Une modification locale peut désormais avoir des conséquences ailleurs : renommer une méthode de réservation bloque les commandes, modifier un événement empêche la production de démarrer, déplacer une image change la clé attendue dans le bucket.",[19,102,103],{},"La commande de notre cliente ressemble alors à ceci :",[105,106,111],"pre",{"className":107,"code":108,"language":109,"meta":110,"style":110},"language-mermaid shiki shiki-themes github-dark-default","flowchart LR\n    C[Cliente] -->|PlaceCakeOrder| O[OrderService]\n    O -->|INSERT order| ODB[(Base commandes)]\n    O -->|ReserveIngredients| S[StockService]\n    S --> SDB[(Base stocks)]\n    O -->|POST \u002Fpayments| P[Prestataire de paiement]\n    O -->|READ cake-designs\u002Forders\u002F42.png| B[(Bucket créations)]\n    O -->|cake.order.confirmed| Q{{Bus d’événements}}\n    Q --> PR[ProductionService]\n    PR --> PDB[(Planning du laboratoire)]\n    PR -->|cake.production.scheduled| Q\n    Q --> N[NotificationService]\n    N -->|Envoyer la confirmation| X[Prestataire de messagerie]\n","mermaid","",[67,112,113,121,127,133,139,145,151,157,163,169,175,181,187],{"__ignoreMap":110},[114,115,118],"span",{"class":116,"line":117},"line",1,[114,119,120],{},"flowchart LR\n",[114,122,124],{"class":116,"line":123},2,[114,125,126],{},"    C[Cliente] -->|PlaceCakeOrder| O[OrderService]\n",[114,128,130],{"class":116,"line":129},3,[114,131,132],{},"    O -->|INSERT order| ODB[(Base commandes)]\n",[114,134,136],{"class":116,"line":135},4,[114,137,138],{},"    O -->|ReserveIngredients| S[StockService]\n",[114,140,142],{"class":116,"line":141},5,[114,143,144],{},"    S --> SDB[(Base stocks)]\n",[114,146,148],{"class":116,"line":147},6,[114,149,150],{},"    O -->|POST \u002Fpayments| P[Prestataire de paiement]\n",[114,152,154],{"class":116,"line":153},7,[114,155,156],{},"    O -->|READ cake-designs\u002Forders\u002F42.png| B[(Bucket créations)]\n",[114,158,160],{"class":116,"line":159},8,[114,161,162],{},"    O -->|cake.order.confirmed| Q{{Bus d’événements}}\n",[114,164,166],{"class":116,"line":165},9,[114,167,168],{},"    Q --> PR[ProductionService]\n",[114,170,172],{"class":116,"line":171},10,[114,173,174],{},"    PR --> PDB[(Planning du laboratoire)]\n",[114,176,178],{"class":116,"line":177},11,[114,179,180],{},"    PR -->|cake.production.scheduled| Q\n",[114,182,184],{"class":116,"line":183},12,[114,185,186],{},"    Q --> N[NotificationService]\n",[114,188,190],{"class":116,"line":189},13,[114,191,192],{},"    N -->|Envoyer la confirmation| X[Prestataire de messagerie]\n",[19,194,195],{},"Ce diagramme est facile à dessiner pour un exemple. Dans un vrai système, il pose plusieurs questions : est-il encore à jour ? D’où viennent ses flèches ? Les dépendances représentées ont-elles été vérifiées ? Et que manque-t-il parce que personne ne savait qu’il fallait le dessiner ?",[197,198,200],"h3",{"id":199},"le-réflexe-du-test-de-bout-en-bout","Le réflexe du test de bout en bout",[19,202,203],{},"Une réponse majoritairement établie consiste à tester toute la commande de bout en bout. Nous déployons tous les services, préparons leurs données, configurons le bus, le stockage et les partenaires, puis jouons le parcours de la cliente.",[19,205,206],{},"Ce test E2E apporte une preuve précieuse : le parcours fonctionne dans cet environnement. Mais plus la boulangerie s’agrandit, plus cette preuve coûte cher à obtenir. L’environnement devient difficile à reproduire, les données partagées rendent les tests sensibles les uns aux autres et, lorsqu’un scénario échoue, il faut remonter toute la chaîne avant de trouver l’atelier responsable.",[19,208,209,210],{},"Nous ne voulons pas abandonner les tests E2E. Nous voulons répondre à une question complémentaire : ",[26,211,212],{},"comment comprendre précisément chaque maillon sans devoir démarrer toute la chaîne ?",[214,215,216,231],"table",{},[217,218,219],"thead",{},[220,221,222,225,228],"tr",{},[223,224],"th",{},[223,226,227],{},"Test E2E",[223,229,230],{},"Test de maillon",[232,233,234,246,257,268,279],"tbody",{},[220,235,236,240,243],{},[237,238,239],"td",{},"Point de vue",[237,241,242],{},"Parcours global",[237,244,245],{},"Comportement d’un service",[220,247,248,251,254],{},[237,249,250],{},"Environnement",[237,252,253],{},"Plusieurs services déployés",[237,255,256],{},"Service réel, frontières maîtrisées",[220,258,259,262,265],{},[237,260,261],{},"Preuve principale",[237,263,264],{},"Le parcours complet fonctionne",[237,266,267],{},"Le service répond et interagit comme attendu",[220,269,270,273,276],{},[237,271,272],{},"Diagnostic",[237,274,275],{},"Réparti sur toute la chaîne",[237,277,278],{},"Rattaché au maillon testé",[220,280,281,284,287],{},[237,282,283],{},"Dépendances",[237,285,286],{},"Réelles ou partagées",[237,288,289],{},"Simulées, observées et vérifiables",[53,291,293],{"id":292},"tester-un-atelier-sans-reconstruire-toute-la-boulangerie","Tester un atelier sans reconstruire toute la boulangerie",[19,295,296,297,299],{},"Prenons ",[67,298,69],{},". Son test de maillon l’appelle par son véritable point d’entrée, l’API HTTP, avec une requête représentative. Le code du service s’exécute réellement, mais ses frontières sont placées sous contrôle : base de test, clients internes et externes simulés, bus local et bucket local.",[19,301,302],{},"Le scénario métier reste lisible par l’équipe :",[105,304,308],{"className":305,"code":306,"language":307,"meta":110,"style":110},"language-gherkin shiki shiki-themes github-dark-default","Rule: Commander un gâteau personnalisé\n\n  Scenario: La commande est acceptée lorsque les ingrédients sont disponibles\n    Given les ingrédients du gâteau sont disponibles\n    And le paiement sera accepté\n    And l'image \"orders\u002F42.png\" existe dans le bucket des créations\n    When la cliente commande un gâteau pour 10 personnes\n    Then la commande est confirmée\n    And un événement de commande confirmée est publié\n","gherkin",[67,309,310,315,321,326,331,336,341,346,351],{"__ignoreMap":110},[114,311,312],{"class":116,"line":117},[114,313,314],{},"Rule: Commander un gâteau personnalisé\n",[114,316,317],{"class":116,"line":123},[114,318,320],{"emptyLinePlaceholder":319},true,"\n",[114,322,323],{"class":116,"line":129},[114,324,325],{},"  Scenario: La commande est acceptée lorsque les ingrédients sont disponibles\n",[114,327,328],{"class":116,"line":135},[114,329,330],{},"    Given les ingrédients du gâteau sont disponibles\n",[114,332,333],{"class":116,"line":141},[114,334,335],{},"    And le paiement sera accepté\n",[114,337,338],{"class":116,"line":147},[114,339,340],{},"    And l'image \"orders\u002F42.png\" existe dans le bucket des créations\n",[114,342,343],{"class":116,"line":153},[114,344,345],{},"    When la cliente commande un gâteau pour 10 personnes\n",[114,347,348],{"class":116,"line":159},[114,349,350],{},"    Then la commande est confirmée\n",[114,352,353],{"class":116,"line":165},[114,354,355],{},"    And un événement de commande confirmée est publié\n",[19,357,358],{},"Ce scénario vérifie le résultat attendu. Pendant son exécution, les adaptateurs de test voient également passer les effets techniques :",[105,360,362],{"className":107,"code":361,"language":109,"meta":110,"style":110},"sequenceDiagram\n    participant C as Scénario Cucumber\n    participant F as ScenarioFlow\n    participant O as OrderService\n    participant DB as Base SQL\n    participant S as StockService simulé\n    participant P as Paiement simulé\n    participant B as Bucket local\n    participant Q as Bus local\n\n    C->>F: attache PlaceCakeOrder + X-Request-Id\n    C->>O: PlaceCakeOrder\n    O->>DB: INSERT INTO orders ...\n    DB-->>F: SqlOperation\n    O->>S: ReserveIngredients\n    S-->>F: ClientOperation.InternalCall\n    O->>P: POST \u002Fpayments\n    P-->>F: ClientOperation.ExternalCall\n    O->>B: READ cake-designs\u002Forders\u002F42.png\n    B-->>F: BucketOperation.Read\n    O->>Q: cake.order.confirmed\n    Q-->>F: BusOperation.Publish\n    O-->>C: commande confirmée\n    C->>F: attache Response\n",[67,363,364,369,374,379,384,389,394,399,404,409,413,418,423,428,434,440,446,452,458,464,470,476,482,488],{"__ignoreMap":110},[114,365,366],{"class":116,"line":117},[114,367,368],{},"sequenceDiagram\n",[114,370,371],{"class":116,"line":123},[114,372,373],{},"    participant C as Scénario Cucumber\n",[114,375,376],{"class":116,"line":129},[114,377,378],{},"    participant F as ScenarioFlow\n",[114,380,381],{"class":116,"line":135},[114,382,383],{},"    participant O as OrderService\n",[114,385,386],{"class":116,"line":141},[114,387,388],{},"    participant DB as Base SQL\n",[114,390,391],{"class":116,"line":147},[114,392,393],{},"    participant S as StockService simulé\n",[114,395,396],{"class":116,"line":153},[114,397,398],{},"    participant P as Paiement simulé\n",[114,400,401],{"class":116,"line":159},[114,402,403],{},"    participant B as Bucket local\n",[114,405,406],{"class":116,"line":165},[114,407,408],{},"    participant Q as Bus local\n",[114,410,411],{"class":116,"line":171},[114,412,320],{"emptyLinePlaceholder":319},[114,414,415],{"class":116,"line":177},[114,416,417],{},"    C->>F: attache PlaceCakeOrder + X-Request-Id\n",[114,419,420],{"class":116,"line":183},[114,421,422],{},"    C->>O: PlaceCakeOrder\n",[114,424,425],{"class":116,"line":189},[114,426,427],{},"    O->>DB: INSERT INTO orders ...\n",[114,429,431],{"class":116,"line":430},14,[114,432,433],{},"    DB-->>F: SqlOperation\n",[114,435,437],{"class":116,"line":436},15,[114,438,439],{},"    O->>S: ReserveIngredients\n",[114,441,443],{"class":116,"line":442},16,[114,444,445],{},"    S-->>F: ClientOperation.InternalCall\n",[114,447,449],{"class":116,"line":448},17,[114,450,451],{},"    O->>P: POST \u002Fpayments\n",[114,453,455],{"class":116,"line":454},18,[114,456,457],{},"    P-->>F: ClientOperation.ExternalCall\n",[114,459,461],{"class":116,"line":460},19,[114,462,463],{},"    O->>B: READ cake-designs\u002Forders\u002F42.png\n",[114,465,467],{"class":116,"line":466},20,[114,468,469],{},"    B-->>F: BucketOperation.Read\n",[114,471,473],{"class":116,"line":472},21,[114,474,475],{},"    O->>Q: cake.order.confirmed\n",[114,477,479],{"class":116,"line":478},22,[114,480,481],{},"    Q-->>F: BusOperation.Publish\n",[114,483,485],{"class":116,"line":484},23,[114,486,487],{},"    O-->>C: commande confirmée\n",[114,489,491],{"class":116,"line":490},24,[114,492,493],{},"    C->>F: attache Response\n",[19,495,496,497,500,501,504],{},"Le test apporte ainsi deux informations liées : le scénario exprime ",[26,498,499],{},"pourquoi"," le comportement compte pour le métier ; les interactions observées montrent ",[26,502,503],{},"comment"," le service collabore avec le reste du système pour produire ce résultat.",[197,506,508],{"id":507},"du-journal-local-à-lempreinte","Du journal local à l’empreinte",[19,510,511],{},"Cette observation devient l’empreinte du service de commande. Elle peut dire, pour ce scénario :",[61,513,514,517,520,523,526,529,532],{},[64,515,516],{},"quelle méthode a été appelée et avec quelle structure de données ;",[64,518,519],{},"quelle requête SQL a été exécutée ;",[64,521,522],{},"quelle méthode interne a été sollicitée ;",[64,524,525],{},"quelle API externe a reçu un appel ;",[64,527,528],{},"quel objet a été lu dans quel bucket ;",[64,530,531],{},"quel événement a été publié ;",[64,533,534],{},"quelle réponse a été retournée.",[19,536,537],{},"L’empreinte n’est donc pas une liste de dépendances déclarées à la main. Elle est la trace structurée d’un comportement réellement exercé et vérifiable en test.",[53,539,541],{"id":540},"rapprocher-les-empreintes-pour-retrouver-lhistoire-complète","Rapprocher les empreintes pour retrouver l’histoire complète",[19,543,544,545,548,549,552],{},"Le service de stock possède ses propres scénarios. Son empreinte montre que ",[67,546,547],{},"ReserveIngredients"," consulte les quantités disponibles et écrit les réservations en base. Le service de production possède lui aussi ses scénarios : lorsqu’il planifie un gâteau, il consulte le planning du laboratoire et publie ",[67,550,551],{},"cake.production.scheduled",". Enfin, l’empreinte du service de notification révèle l’appel au prestataire de messagerie.",[19,554,555],{},"Chaque empreinte éclaire un atelier. Leur mise en relation permet de retrouver progressivement la circulation de la commande dans la boulangerie :",[105,557,562],{"className":558,"code":559,"language":560,"meta":561,"style":110},"language-plain shiki shiki-themes github-dark-default","Empreinte OrderService\n├── appelle StockService.ReserveIngredients\n├── appelle POST \u002Fpayments\n├── lit cake-designs\u002Forders\u002F42.png\n└── publie cake.order.confirmed\n\nEmpreinte StockService\n└── expose ReserveIngredients\n    ├── lit les quantités disponibles\n    └── écrit une réservation\n\nEmpreinte ProductionService\n└── exécute ScheduleCake\n    ├── écrit dans le planning\n    └── publie cake.production.scheduled\n\nEmpreinte NotificationService\n└── exécute NotifyCustomer\n    └── appelle le prestataire de messagerie\n","plain","text",[67,563,564,569,574,579,584,589,593,598,603,608,613,617,622,627,632,637,641,646,651],{"__ignoreMap":110},[114,565,566],{"class":116,"line":117},[114,567,568],{},"Empreinte OrderService\n",[114,570,571],{"class":116,"line":123},[114,572,573],{},"├── appelle StockService.ReserveIngredients\n",[114,575,576],{"class":116,"line":129},[114,577,578],{},"├── appelle POST \u002Fpayments\n",[114,580,581],{"class":116,"line":135},[114,582,583],{},"├── lit cake-designs\u002Forders\u002F42.png\n",[114,585,586],{"class":116,"line":141},[114,587,588],{},"└── publie cake.order.confirmed\n",[114,590,591],{"class":116,"line":147},[114,592,320],{"emptyLinePlaceholder":319},[114,594,595],{"class":116,"line":153},[114,596,597],{},"Empreinte StockService\n",[114,599,600],{"class":116,"line":159},[114,601,602],{},"└── expose ReserveIngredients\n",[114,604,605],{"class":116,"line":165},[114,606,607],{},"    ├── lit les quantités disponibles\n",[114,609,610],{"class":116,"line":171},[114,611,612],{},"    └── écrit une réservation\n",[114,614,615],{"class":116,"line":177},[114,616,320],{"emptyLinePlaceholder":319},[114,618,619],{"class":116,"line":183},[114,620,621],{},"Empreinte ProductionService\n",[114,623,624],{"class":116,"line":189},[114,625,626],{},"└── exécute ScheduleCake\n",[114,628,629],{"class":116,"line":430},[114,630,631],{},"    ├── écrit dans le planning\n",[114,633,634],{"class":116,"line":436},[114,635,636],{},"    └── publie cake.production.scheduled\n",[114,638,639],{"class":116,"line":442},[114,640,320],{"emptyLinePlaceholder":319},[114,642,643],{"class":116,"line":448},[114,644,645],{},"Empreinte NotificationService\n",[114,647,648],{"class":116,"line":454},[114,649,650],{},"└── exécute NotifyCustomer\n",[114,652,653],{"class":116,"line":460},[114,654,655],{},"    └── appelle le prestataire de messagerie\n",[19,657,658],{},"Une fois ces empreintes versionnées rapprochées, elles peuvent être projetées sous la forme d’un diagramme de séquence. Nous ne regardons plus seulement la liste des voisins de chaque service : nous retrouvons le voyage de la commande et la causalité entre les interactions observées dans chaque maillon.",[105,660,662],{"className":107,"code":661,"language":109,"meta":110,"style":110},"sequenceDiagram\n    actor C as Cliente\n    participant O as OrderService\n    participant ODB as Base commandes\n    participant S as StockService\n    participant SDB as Base stocks\n    participant PAY as Prestataire de paiement\n    participant B as Bucket créations\n    participant Q as Bus d’événements\n    participant PR as ProductionService\n    participant PDB as Planning du laboratoire\n    participant N as NotificationService\n    participant MSG as Prestataire de messagerie\n\n    C->>O: PlaceCakeOrder\n    O->>ODB: INSERT order\n    O->>S: ReserveIngredients\n    S->>SDB: SELECT disponibilités\n    S->>SDB: INSERT réservation\n    S->>O: Ingrédients réservés\n    O->>PAY: POST \u002Fpayments\n    PAY->>O: Paiement accepté\n    O->>B: READ cake-designs\u002Forders\u002F42.png\n    B->>O: Image personnalisée\n    O->>Q: Publier cake.order.confirmed\n    O->>C: Commande confirmée\n\n    Note over Q,PR: Raccordement via le contrat ou la configuration du consommateur\n    Q-->>PR: cake.order.confirmed\n    PR->>PDB: INSERT production_schedule\n    PR->>Q: Publier cake.production.scheduled\n\n    Note over Q,N: Raccordement via le contrat ou la configuration du consommateur\n    Q-->>N: cake.production.scheduled\n    N->>MSG: Envoyer la confirmation\n    MSG->>N: Message accepté\n",[67,663,664,668,673,677,682,687,692,697,702,707,712,717,722,727,731,735,740,744,749,754,759,764,769,773,778,784,790,795,801,807,813,819,824,830,836,842],{"__ignoreMap":110},[114,665,666],{"class":116,"line":117},[114,667,368],{},[114,669,670],{"class":116,"line":123},[114,671,672],{},"    actor C as Cliente\n",[114,674,675],{"class":116,"line":129},[114,676,383],{},[114,678,679],{"class":116,"line":135},[114,680,681],{},"    participant ODB as Base commandes\n",[114,683,684],{"class":116,"line":141},[114,685,686],{},"    participant S as StockService\n",[114,688,689],{"class":116,"line":147},[114,690,691],{},"    participant SDB as Base stocks\n",[114,693,694],{"class":116,"line":153},[114,695,696],{},"    participant PAY as Prestataire de paiement\n",[114,698,699],{"class":116,"line":159},[114,700,701],{},"    participant B as Bucket créations\n",[114,703,704],{"class":116,"line":165},[114,705,706],{},"    participant Q as Bus d’événements\n",[114,708,709],{"class":116,"line":171},[114,710,711],{},"    participant PR as ProductionService\n",[114,713,714],{"class":116,"line":177},[114,715,716],{},"    participant PDB as Planning du laboratoire\n",[114,718,719],{"class":116,"line":183},[114,720,721],{},"    participant N as NotificationService\n",[114,723,724],{"class":116,"line":189},[114,725,726],{},"    participant MSG as Prestataire de messagerie\n",[114,728,729],{"class":116,"line":430},[114,730,320],{"emptyLinePlaceholder":319},[114,732,733],{"class":116,"line":436},[114,734,422],{},[114,736,737],{"class":116,"line":442},[114,738,739],{},"    O->>ODB: INSERT order\n",[114,741,742],{"class":116,"line":448},[114,743,439],{},[114,745,746],{"class":116,"line":454},[114,747,748],{},"    S->>SDB: SELECT disponibilités\n",[114,750,751],{"class":116,"line":460},[114,752,753],{},"    S->>SDB: INSERT réservation\n",[114,755,756],{"class":116,"line":466},[114,757,758],{},"    S->>O: Ingrédients réservés\n",[114,760,761],{"class":116,"line":472},[114,762,763],{},"    O->>PAY: POST \u002Fpayments\n",[114,765,766],{"class":116,"line":478},[114,767,768],{},"    PAY->>O: Paiement accepté\n",[114,770,771],{"class":116,"line":484},[114,772,463],{},[114,774,775],{"class":116,"line":490},[114,776,777],{},"    B->>O: Image personnalisée\n",[114,779,781],{"class":116,"line":780},25,[114,782,783],{},"    O->>Q: Publier cake.order.confirmed\n",[114,785,787],{"class":116,"line":786},26,[114,788,789],{},"    O->>C: Commande confirmée\n",[114,791,793],{"class":116,"line":792},27,[114,794,320],{"emptyLinePlaceholder":319},[114,796,798],{"class":116,"line":797},28,[114,799,800],{},"    Note over Q,PR: Raccordement via le contrat ou la configuration du consommateur\n",[114,802,804],{"class":116,"line":803},29,[114,805,806],{},"    Q-->>PR: cake.order.confirmed\n",[114,808,810],{"class":116,"line":809},30,[114,811,812],{},"    PR->>PDB: INSERT production_schedule\n",[114,814,816],{"class":116,"line":815},31,[114,817,818],{},"    PR->>Q: Publier cake.production.scheduled\n",[114,820,822],{"class":116,"line":821},32,[114,823,320],{"emptyLinePlaceholder":319},[114,825,827],{"class":116,"line":826},33,[114,828,829],{},"    Note over Q,N: Raccordement via le contrat ou la configuration du consommateur\n",[114,831,833],{"class":116,"line":832},34,[114,834,835],{},"    Q-->>N: cake.production.scheduled\n",[114,837,839],{"class":116,"line":838},35,[114,840,841],{},"    N->>MSG: Envoyer la confirmation\n",[114,843,845],{"class":116,"line":844},36,[114,846,847],{},"    MSG->>N: Message accepté\n",[19,849,850],{},"Les flèches pleines proviennent des interactions visibles dans les empreintes des services concernés. Les flèches en pointillés représentent le raccordement d’un événement publié avec son consommateur ; cette relation requiert le contrat ou la configuration de souscription en complément de l’empreinte actuelle. Le diagramme obtenu ne prétend donc pas être une trace distribuée capturée lors d’une exécution unique : il recompose un parcours cohérent à partir de preuves locales.",[19,852,853],{},"Cette projection rend immédiatement visibles les appels en cascade, les passages synchrones et asynchrones, ainsi que le service à l’origine de chaque accès aux données ou à une ressource externe. Elle apporte une lecture proche de celle recherchée dans un test E2E, tout en permettant de revenir à l’empreinte et au scénario précis qui justifient chaque segment du parcours.",[19,855,856],{},"Le rapprochement rend les interdépendances concrètes. Il devient possible d’identifier les services touchés par une évolution, de repérer un appel interne sans point d’entrée correspondant, de retrouver les producteurs d’un événement, ou encore de découvrir un bucket et une requête SQL absents de la documentation d’architecture.",[19,858,859,860,863,864,867,868,870],{},"Il faut toutefois distinguer ce qui est enregistré de ce qui est déduit. L’empreinte actuelle capture notamment les événements ",[26,861,862],{},"publiés",". Elle ne reconstruit pas automatiquement toute leur chaîne de consommation. Relier",[67,865,866],{},"cake.order.confirmed"," au déclenchement de ",[67,869,81],{}," demande aussi de connaître le contrat ou la configuration du consommateur. La carte globale devient plus précise à mesure que les sources de connaissance sont rapprochées.",[19,872,873],{},"Et surtout, une dépendance jamais traversée par un scénario ne peut pas apparaître. Une zone blanche dans la carte peut signifier qu’il ne se passe rien — ou simplement que nous ne l’avons pas encore observée.",[53,875,877,878,881],{"id":876},"dans-les-coulisses-comment-scenarioflow-construit-lempreinte","Dans les coulisses : comment ",[67,879,880],{},"ScenarioFlow"," construit l’empreinte",[19,883,884],{},"La boulangerie rend le problème visible. Revenons maintenant au mécanisme qui transforme un scénario Cucumber en artefact exploitable :",[105,886,888],{"className":558,"code":887,"language":560,"meta":561,"style":110},"TestSuiteFlow\n└── ScenarioFlow                         un scénario Cucumber\n    ├── ScenarioDefinition               feature, rule, scénario, tags, URI\n    └── RequestFlow                      un appel entrant ou un worker\n        ├── Request                      méthode, corps, en-têtes, identifiant\n        └── RequestFact\u003C?>               faits observés dans l'ordre de leur exécution\n            ├── SqlOperation\n            ├── ClientOperation\n            ├── BusOperation\n            ├── BucketOperation\n            ├── Response\n            └── Worker.Result\n",[67,889,890,895,900,905,910,915,920,925,930,935,940,945],{"__ignoreMap":110},[114,891,892],{"class":116,"line":117},[114,893,894],{},"TestSuiteFlow\n",[114,896,897],{"class":116,"line":123},[114,898,899],{},"└── ScenarioFlow                         un scénario Cucumber\n",[114,901,902],{"class":116,"line":129},[114,903,904],{},"    ├── ScenarioDefinition               feature, rule, scénario, tags, URI\n",[114,906,907],{"class":116,"line":135},[114,908,909],{},"    └── RequestFlow                      un appel entrant ou un worker\n",[114,911,912],{"class":116,"line":141},[114,913,914],{},"        ├── Request                      méthode, corps, en-têtes, identifiant\n",[114,916,917],{"class":116,"line":147},[114,918,919],{},"        └── RequestFact\u003C?>               faits observés dans l'ordre de leur exécution\n",[114,921,922],{"class":116,"line":153},[114,923,924],{},"            ├── SqlOperation\n",[114,926,927],{"class":116,"line":159},[114,928,929],{},"            ├── ClientOperation\n",[114,931,932],{"class":116,"line":165},[114,933,934],{},"            ├── BusOperation\n",[114,936,937],{"class":116,"line":171},[114,938,939],{},"            ├── BucketOperation\n",[114,941,942],{"class":116,"line":177},[114,943,944],{},"            ├── Response\n",[114,946,947],{"class":116,"line":183},[114,948,949],{},"            └── Worker.Result\n",[19,951,952,954,955,958,959,961],{},[67,953,880],{}," définit le contrat. Un flux représente un scénario, accepte ses requêtes et permet de retrouver le ",[67,956,957],{},"RequestFlow"," courant ou celui qui correspond à un identifiant précis. Chaque ",[67,960,957],{}," conserve ensuite la requête entrante et les faits observés pendant son traitement.",[19,963,964,965,968],{},"Son implémentation ",[67,966,967],{},"GherkinScenarioFlow"," utilise des files concurrentes. Un scénario peut contenir plusieurs appels, y compris en parallèle, tout en conservant les faits associés à chacun.",[197,970,972],{"id":971},"ouvrir-la-bonne-fenêtre-dobservation","Ouvrir la bonne fenêtre d’observation",[19,974,975],{},"La collecte suit le cycle de vie Cucumber :",[61,977,978,988,998,1005,1010,1017],{},[64,979,980,983,984,987],{},[67,981,982],{},"ScenarioLifecycle"," identifie la feature, la ",[67,985,986],{},"Rule",", le scénario, ses tags et son URI ;",[64,989,990,991,994,995,997],{},"le hook ",[67,992,993],{},"PREPARE"," crée un nouveau ",[67,996,967],{}," ;",[64,999,1000,1001,1004],{},"les listeners SQL, bus, clients et storage commencent à écouter ",[26,1002,1003],{},"après"," le chargement des fixtures ;",[64,1006,1007,1008,997],{},"le point d’entrée est exécuté et les faits sont attachés à son ",[67,1009,957],{},[64,1011,1012,1013,1016],{},"les listeners s’arrêtent ",[26,1014,1015],{},"avant"," le nettoyage des fixtures ;",[64,1018,1019,1020,29],{},"le scénario est transmis pour écriture dans le catalogue d’empreintes ",[67,1021,1022],{},"Protobuf",[19,1024,1025],{},"Les requêtes utilisées pour préparer ou vider la base ne décrivent pas le comportement du service. Cette fenêtre évite donc de les confondre avec les interactions produites par la commande de gâteau.",[197,1027,1029],{"id":1028},"corréler-les-faits-avec-la-bonne-requête","Corréler les faits avec la bonne requête",[19,1031,1032],{},"Dans un scénario séquentiel, un fait peut être rattaché à la dernière requête ouverte. Cela ne suffit plus lorsque plusieurs clientes commandent simultanément ou lorsque des actions internes sont réalisées en concurrence.",[19,1034,1035,1036,1039,1040,1043,1044,1047],{},"Chaque ",[67,1037,1038],{},"Request"," reçoit un identifiant ordonné dans le temps. Le client de test le propage dans ",[67,1041,1042],{},"X-Request-Id",". Lorsqu’un listener cherche la requête courante, ",[67,1045,1046],{},"CucumberTestSuiteFlow"," consulte le contexte de métadonnées :",[61,1049,1050,1059],{},[64,1051,1052,1053,1055,1056,1058],{},"si ",[67,1054,1042],{}," est disponible, il retrouve le ",[67,1057,957],{}," correspondant ;",[64,1060,1061],{},"sinon, il se rabat sur la dernière requête attachée.",[19,1063,1064],{},"Ainsi, le paiement et la réservation d’ingrédients de la commande 42 ne viennent pas enrichir par erreur l’empreinte de la commande 43. Cette garantie dépend de la propagation correcte de la métadonnée jusqu’à la publication de l’événement d’audit.",[197,1066,1068],{"id":1067},"une-mécanique-commune-pour-toutes-les-frontières","Une mécanique commune pour toutes les frontières",[19,1070,1071,1072,1074,1075,1078],{},"Chaque adaptateur instrumenté publie un événement d’audit. Un listener l’attache au ",[67,1073,957],{}," courant, puis ",[67,1076,1077],{},"RequestFact"," le présente à un visiteur typé.",[214,1080,1081,1097],{},[217,1082,1083],{},[220,1084,1085,1088,1091,1094],{},[223,1086,1087],{},"Frontière observée",[223,1089,1090],{},"Fait dans le code (Java)",[223,1092,1093],{},"Interaction Protobuf",[223,1095,1096],{},"Données conservées",[232,1098,1099,1113,1127,1141,1155],{},[220,1100,1101,1104,1107,1110],{},[237,1102,1103],{},"Base de données",[237,1105,1106],{},"SqlOperation",[237,1108,1109],{},"SqlInteraction",[237,1111,1112],{},"statement SQL",[220,1114,1115,1118,1121,1124],{},[237,1116,1117],{},"Service interne",[237,1119,1120],{},"ClientOperation.InternalCall",[237,1122,1123],{},"InternalCallInteraction",[237,1125,1126],{},"méthode, en-têtes, corps, réponse",[220,1128,1129,1132,1135,1138],{},[237,1130,1131],{},"Partenaire externe",[237,1133,1134],{},"ClientOperation.ExternalCall",[237,1136,1137],{},"ExternalCallInteraction",[237,1139,1140],{},"méthode HTTP, URI, en-têtes, corps, réponse",[220,1142,1143,1146,1149,1152],{},[237,1144,1145],{},"Bus",[237,1147,1148],{},"BusOperation.Publish",[237,1150,1151],{},"EventInteraction",[237,1153,1154],{},"topic, payload",[220,1156,1157,1160,1163,1166],{},[237,1158,1159],{},"Stockage objet",[237,1161,1162],{},"BucketOperation",[237,1164,1165],{},"BucketInteraction",[237,1167,1168],{},"bucket, clé, opération",[19,1170,1171,1172,1175,1176,1179,1180,1183,1184,29],{},"Le proxy ",[67,1173,1174],{},"DataSource"," observe le SQL avant son exécution. Les clients internes et les serveurs externes WireMock publient leurs appels, qu’ils réussissent ou échouent. Le bus et le bucket locaux publient respectivement les messages envoyés et les opérations ",[67,1177,1178],{},"READ",", ",[67,1181,1182],{},"WRITE"," ou ",[67,1185,1186],{},"DELETE",[19,1188,1189],{},"Le contenu d’un fichier écrit peut être utilisé par les assertions du test, mais l’empreinte du bucket ne conserve que son emplacement et l’opération. À l’inverse, les appels internes et externes peuvent contenir des en-têtes, des corps et des réponses : les scénarios doivent donc employer des données synthétiques et ne jamais y introduire de secrets.",[197,1191,1193],{"id":1192},"structurer-le-catalogue-dempreinte-protobuf","Structurer le catalogue d’empreinte Protobuf",[19,1195,1196,1199,1200,1203,1204,1206,1207,1210],{},[67,1197,1198],{},"ProtoScenarioFlowPrinter"," transforme les faits grâce au pattern Visitor ",[67,1201,1202],{},"FactToInteractionVisitor",". Le schéma ",[67,1205,1022],{}," transforme un fait en ",[67,1208,1209],{},"Interaction"," .",[19,1212,1213],{},"Le résultat suit cette hiérarchie :",[105,1215,1217],{"className":558,"code":1216,"language":560,"meta":561,"style":110},"Catalog\n└── Endpoint                         regroupé par URI de feature\n    └── Rule                         règle Gherkin ou \"default\"\n        └── Scenario                 exécution d’un scénario\n            └── Step                 requête entrante\n                ├── called_method\n                ├── body\n                ├── interactions[]\n                └── response\n",[67,1218,1219,1224,1229,1234,1239,1244,1249,1254,1259],{"__ignoreMap":110},[114,1220,1221],{"class":116,"line":117},[114,1222,1223],{},"Catalog\n",[114,1225,1226],{"class":116,"line":123},[114,1227,1228],{},"└── Endpoint                         regroupé par URI de feature\n",[114,1230,1231],{"class":116,"line":129},[114,1232,1233],{},"    └── Rule                         règle Gherkin ou \"default\"\n",[114,1235,1236],{"class":116,"line":135},[114,1237,1238],{},"        └── Scenario                 exécution d’un scénario\n",[114,1240,1241],{"class":116,"line":141},[114,1242,1243],{},"            └── Step                 requête entrante\n",[114,1245,1246],{"class":116,"line":147},[114,1247,1248],{},"                ├── called_method\n",[114,1250,1251],{"class":116,"line":153},[114,1252,1253],{},"                ├── body\n",[114,1255,1256],{"class":116,"line":159},[114,1257,1258],{},"                ├── interactions[]\n",[114,1260,1261],{"class":116,"line":165},[114,1262,1263],{},"                └── response\n",[19,1265,1266],{},"Les payloads JSON gardent leur contenu et une représentation de leur structure. Une commande comme :",[105,1268,1272],{"className":1269,"code":1270,"language":1271,"meta":110,"style":110},"language-json shiki shiki-themes github-dark-default","{\n  \"customerId\": \"customer-7\",\n  \"cake\": {\n    \"servings\": 10,\n    \"flavours\": [\"chocolate\", \"raspberry\"]\n  },\n  \"design\": {\n    \"bucket\": \"cake-designs\",\n    \"key\": \"orders\u002F42.png\"\n  }\n}\n","json",[67,1273,1274,1280,1296,1304,1317,1336,1341,1348,1360,1370,1375],{"__ignoreMap":110},[114,1275,1276],{"class":116,"line":117},[114,1277,1279],{"class":1278},"sZEs4","{\n",[114,1281,1282,1286,1289,1293],{"class":116,"line":123},[114,1283,1285],{"class":1284},"sPWt5","  \"customerId\"",[114,1287,1288],{"class":1278},": ",[114,1290,1292],{"class":1291},"s9uIt","\"customer-7\"",[114,1294,1295],{"class":1278},",\n",[114,1297,1298,1301],{"class":116,"line":129},[114,1299,1300],{"class":1284},"  \"cake\"",[114,1302,1303],{"class":1278},": {\n",[114,1305,1306,1309,1311,1315],{"class":116,"line":135},[114,1307,1308],{"class":1284},"    \"servings\"",[114,1310,1288],{"class":1278},[114,1312,1314],{"class":1313},"sFSAA","10",[114,1316,1295],{"class":1278},[114,1318,1319,1322,1325,1328,1330,1333],{"class":116,"line":141},[114,1320,1321],{"class":1284},"    \"flavours\"",[114,1323,1324],{"class":1278},": [",[114,1326,1327],{"class":1291},"\"chocolate\"",[114,1329,1179],{"class":1278},[114,1331,1332],{"class":1291},"\"raspberry\"",[114,1334,1335],{"class":1278},"]\n",[114,1337,1338],{"class":116,"line":147},[114,1339,1340],{"class":1278},"  },\n",[114,1342,1343,1346],{"class":116,"line":153},[114,1344,1345],{"class":1284},"  \"design\"",[114,1347,1303],{"class":1278},[114,1349,1350,1353,1355,1358],{"class":116,"line":159},[114,1351,1352],{"class":1284},"    \"bucket\"",[114,1354,1288],{"class":1278},[114,1356,1357],{"class":1291},"\"cake-designs\"",[114,1359,1295],{"class":1278},[114,1361,1362,1365,1367],{"class":116,"line":165},[114,1363,1364],{"class":1284},"    \"key\"",[114,1366,1288],{"class":1278},[114,1368,1369],{"class":1291},"\"orders\u002F42.png\"\n",[114,1371,1372],{"class":116,"line":171},[114,1373,1374],{"class":1278},"  }\n",[114,1376,1377],{"class":116,"line":177},[114,1378,1379],{"class":1278},"}\n",[19,1381,1382],{},"produit notamment les chemins suivants :",[105,1384,1386],{"className":558,"code":1385,"language":560,"meta":561,"style":110},"customerId\ncake.servings\ncake.flavours[]\ndesign.bucket\ndesign.key\n",[67,1387,1388,1393,1398,1403,1408],{"__ignoreMap":110},[114,1389,1390],{"class":116,"line":117},[114,1391,1392],{},"customerId\n",[114,1394,1395],{"class":116,"line":123},[114,1396,1397],{},"cake.servings\n",[114,1399,1400],{"class":116,"line":129},[114,1401,1402],{},"cake.flavours[]\n",[114,1404,1405],{"class":116,"line":135},[114,1406,1407],{},"design.bucket\n",[114,1409,1410],{"class":116,"line":141},[114,1411,1412],{},"design.key\n",[19,1414,1415],{},"Cette double représentation permet de documenter les données observées tout en comparant leur forme. Si un contenu n’est pas un JSON analysable, comme un statement SQL, sa structure reste vide et son contenu brut est conservé.",[19,1417,1418,1419,1421],{},"Une vue JSON simplifiée de l’empreinte d’",[67,1420,69],{}," ressemblerait à ceci :",[105,1423,1425],{"className":1269,"code":1424,"language":1271,"meta":110,"style":110},"{\n  \"calledMethod\": \"OrderService.PlaceCakeOrder\",\n  \"body\": {\n    \"structure\": [\"customerId\", \"cake.servings\", \"cake.flavours[]\", \"design.bucket\", \"design.key\"],\n    \"content\": {\n      \"customerId\": \"customer-7\",\n      \"cake\": { \"servings\": 10, \"flavours\": [\"chocolate\", \"raspberry\"] },\n      \"design\": { \"bucket\": \"cake-designs\", \"key\": \"orders\u002F42.png\" }\n    }\n  },\n  \"interactions\": [\n    { \"sql\": { \"query\": { \"content\": \"insert into orders ...\" } } },\n    { \"internalCall\": { \"method\": \"StockService.ReserveIngredients\" } },\n    { \"externalCall\": { \"httpMethod\": \"POST\", \"uri\": \"\u002Fpayments\" } },\n    { \"bucket\": { \"bucket\": \"cake-designs\", \"key\": \"orders\u002F42.png\", \"operation\": \"READ\" } },\n    { \"event\": { \"eventName\": \"cake.order.confirmed\" } }\n  ],\n  \"response\": {\n    \"structure\": [\"orderId\", \"status\", \"pickupAt\"],\n    \"content\": { \"orderId\": 42, \"status\": \"CONFIRMED\", \"pickupAt\": \"2024-06-15T16:00:00Z\" }\n  }\n}\n",[67,1426,1427,1431,1443,1450,1483,1490,1501,1532,1559,1564,1568,1576,1602,1622,1651,1685,1705,1710,1717,1738,1771,1775],{"__ignoreMap":110},[114,1428,1429],{"class":116,"line":117},[114,1430,1279],{"class":1278},[114,1432,1433,1436,1438,1441],{"class":116,"line":123},[114,1434,1435],{"class":1284},"  \"calledMethod\"",[114,1437,1288],{"class":1278},[114,1439,1440],{"class":1291},"\"OrderService.PlaceCakeOrder\"",[114,1442,1295],{"class":1278},[114,1444,1445,1448],{"class":116,"line":129},[114,1446,1447],{"class":1284},"  \"body\"",[114,1449,1303],{"class":1278},[114,1451,1452,1455,1457,1460,1462,1465,1467,1470,1472,1475,1477,1480],{"class":116,"line":135},[114,1453,1454],{"class":1284},"    \"structure\"",[114,1456,1324],{"class":1278},[114,1458,1459],{"class":1291},"\"customerId\"",[114,1461,1179],{"class":1278},[114,1463,1464],{"class":1291},"\"cake.servings\"",[114,1466,1179],{"class":1278},[114,1468,1469],{"class":1291},"\"cake.flavours[]\"",[114,1471,1179],{"class":1278},[114,1473,1474],{"class":1291},"\"design.bucket\"",[114,1476,1179],{"class":1278},[114,1478,1479],{"class":1291},"\"design.key\"",[114,1481,1482],{"class":1278},"],\n",[114,1484,1485,1488],{"class":116,"line":141},[114,1486,1487],{"class":1284},"    \"content\"",[114,1489,1303],{"class":1278},[114,1491,1492,1495,1497,1499],{"class":116,"line":147},[114,1493,1494],{"class":1284},"      \"customerId\"",[114,1496,1288],{"class":1278},[114,1498,1292],{"class":1291},[114,1500,1295],{"class":1278},[114,1502,1503,1506,1509,1512,1514,1516,1518,1521,1523,1525,1527,1529],{"class":116,"line":153},[114,1504,1505],{"class":1284},"      \"cake\"",[114,1507,1508],{"class":1278},": { ",[114,1510,1511],{"class":1284},"\"servings\"",[114,1513,1288],{"class":1278},[114,1515,1314],{"class":1313},[114,1517,1179],{"class":1278},[114,1519,1520],{"class":1284},"\"flavours\"",[114,1522,1324],{"class":1278},[114,1524,1327],{"class":1291},[114,1526,1179],{"class":1278},[114,1528,1332],{"class":1291},[114,1530,1531],{"class":1278},"] },\n",[114,1533,1534,1537,1539,1542,1544,1546,1548,1551,1553,1556],{"class":116,"line":159},[114,1535,1536],{"class":1284},"      \"design\"",[114,1538,1508],{"class":1278},[114,1540,1541],{"class":1284},"\"bucket\"",[114,1543,1288],{"class":1278},[114,1545,1357],{"class":1291},[114,1547,1179],{"class":1278},[114,1549,1550],{"class":1284},"\"key\"",[114,1552,1288],{"class":1278},[114,1554,1555],{"class":1291},"\"orders\u002F42.png\"",[114,1557,1558],{"class":1278}," }\n",[114,1560,1561],{"class":116,"line":165},[114,1562,1563],{"class":1278},"    }\n",[114,1565,1566],{"class":116,"line":171},[114,1567,1340],{"class":1278},[114,1569,1570,1573],{"class":116,"line":177},[114,1571,1572],{"class":1284},"  \"interactions\"",[114,1574,1575],{"class":1278},": [\n",[114,1577,1578,1581,1584,1586,1589,1591,1594,1596,1599],{"class":116,"line":183},[114,1579,1580],{"class":1278},"    { ",[114,1582,1583],{"class":1284},"\"sql\"",[114,1585,1508],{"class":1278},[114,1587,1588],{"class":1284},"\"query\"",[114,1590,1508],{"class":1278},[114,1592,1593],{"class":1284},"\"content\"",[114,1595,1288],{"class":1278},[114,1597,1598],{"class":1291},"\"insert into orders ...\"",[114,1600,1601],{"class":1278}," } } },\n",[114,1603,1604,1606,1609,1611,1614,1616,1619],{"class":116,"line":189},[114,1605,1580],{"class":1278},[114,1607,1608],{"class":1284},"\"internalCall\"",[114,1610,1508],{"class":1278},[114,1612,1613],{"class":1284},"\"method\"",[114,1615,1288],{"class":1278},[114,1617,1618],{"class":1291},"\"StockService.ReserveIngredients\"",[114,1620,1621],{"class":1278}," } },\n",[114,1623,1624,1626,1629,1631,1634,1636,1639,1641,1644,1646,1649],{"class":116,"line":430},[114,1625,1580],{"class":1278},[114,1627,1628],{"class":1284},"\"externalCall\"",[114,1630,1508],{"class":1278},[114,1632,1633],{"class":1284},"\"httpMethod\"",[114,1635,1288],{"class":1278},[114,1637,1638],{"class":1291},"\"POST\"",[114,1640,1179],{"class":1278},[114,1642,1643],{"class":1284},"\"uri\"",[114,1645,1288],{"class":1278},[114,1647,1648],{"class":1291},"\"\u002Fpayments\"",[114,1650,1621],{"class":1278},[114,1652,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671,1673,1675,1678,1680,1683],{"class":116,"line":436},[114,1654,1580],{"class":1278},[114,1656,1541],{"class":1284},[114,1658,1508],{"class":1278},[114,1660,1541],{"class":1284},[114,1662,1288],{"class":1278},[114,1664,1357],{"class":1291},[114,1666,1179],{"class":1278},[114,1668,1550],{"class":1284},[114,1670,1288],{"class":1278},[114,1672,1555],{"class":1291},[114,1674,1179],{"class":1278},[114,1676,1677],{"class":1284},"\"operation\"",[114,1679,1288],{"class":1278},[114,1681,1682],{"class":1291},"\"READ\"",[114,1684,1621],{"class":1278},[114,1686,1687,1689,1692,1694,1697,1699,1702],{"class":116,"line":442},[114,1688,1580],{"class":1278},[114,1690,1691],{"class":1284},"\"event\"",[114,1693,1508],{"class":1278},[114,1695,1696],{"class":1284},"\"eventName\"",[114,1698,1288],{"class":1278},[114,1700,1701],{"class":1291},"\"cake.order.confirmed\"",[114,1703,1704],{"class":1278}," } }\n",[114,1706,1707],{"class":116,"line":448},[114,1708,1709],{"class":1278},"  ],\n",[114,1711,1712,1715],{"class":116,"line":454},[114,1713,1714],{"class":1284},"  \"response\"",[114,1716,1303],{"class":1278},[114,1718,1719,1721,1723,1726,1728,1731,1733,1736],{"class":116,"line":460},[114,1720,1454],{"class":1284},[114,1722,1324],{"class":1278},[114,1724,1725],{"class":1291},"\"orderId\"",[114,1727,1179],{"class":1278},[114,1729,1730],{"class":1291},"\"status\"",[114,1732,1179],{"class":1278},[114,1734,1735],{"class":1291},"\"pickupAt\"",[114,1737,1482],{"class":1278},[114,1739,1740,1742,1744,1746,1748,1751,1753,1755,1757,1760,1762,1764,1766,1769],{"class":116,"line":466},[114,1741,1487],{"class":1284},[114,1743,1508],{"class":1278},[114,1745,1725],{"class":1284},[114,1747,1288],{"class":1278},[114,1749,1750],{"class":1313},"42",[114,1752,1179],{"class":1278},[114,1754,1730],{"class":1284},[114,1756,1288],{"class":1278},[114,1758,1759],{"class":1291},"\"CONFIRMED\"",[114,1761,1179],{"class":1278},[114,1763,1735],{"class":1284},[114,1765,1288],{"class":1278},[114,1767,1768],{"class":1291},"\"2024-06-15T16:00:00Z\"",[114,1770,1558],{"class":1278},[114,1772,1773],{"class":116,"line":472},[114,1774,1374],{"class":1278},[114,1776,1777],{"class":116,"line":478},[114,1778,1379],{"class":1278},[19,1780,1781],{},"Toutes les dépendances restent ainsi rattachées à la méthode et au scénario qui les ont provoquées.",[53,1783,1785],{"id":1784},"des-tests-à-un-artefact-versionné","Des tests à un artefact versionné",[19,1787,1788,1789,1791,1792,1795,1796,1799],{},"Après chaque scénario, ",[67,1790,1198],{}," ajoute son flux au catalogue. Après toute la suite, le hook Cucumber ",[67,1793,1794],{},"@AfterAll"," écrit le catalogue binaire dans ",[67,1797,1798],{},"target\u002Ffingerprint\u002Fcatalog.pb"," et en affiche une représentation JSON dans les logs.",[19,1801,1802,1803,1806,1807,1810],{},"La CI exécute les features séparément selon leur contexte. Chaque shard produit son catalogue, que l’image de test copie sous le nom ",[67,1804,1805],{},"fingerprint.pb",". Les empreintes sont ensuite vérifiées, regroupées par contexte dans",[67,1808,1809],{},"fingerprints.tar.gz",", attachées à la prérelease, puis promues avec la release du service.",[19,1812,1813,1814,1817,1818,1820,1821,29],{},"Le premier schéma se place volontairement ",[26,1815,1816],{},"à l’échelle d’un seul service",". Prenons ",[67,1819,75],{}," : ses scénarios de réservation, de réapprovisionnement et d’inventaire peuvent être exécutés dans des shards distincts, mais ils alimentent tous l’artefact associé à une même release de ",[67,1822,75],{},[105,1824,1826],{"className":107,"code":1825,"language":109,"meta":110,"style":110},"flowchart LR\n    A[Scénarios Cucumber de StockService] --> B[Tests par sous-contexte]\n    B --> C1[Réservation]\n    B --> C2[Réapprovisionnement]\n    B --> C3[Inventaire]\n    C1 --> D1[fingerprint.pb]\n    C2 --> D2[fingerprint.pb]\n    C3 --> D3[fingerprint.pb]\n    D1 --> E[Regroupement]\n    D2 --> E\n    D3 --> E\n    E --> F[fingerprints.tar.gz]\n    F --> G[Prérelease]\n    G --> H[Release]\n",[67,1827,1828,1832,1837,1842,1847,1852,1857,1862,1867,1872,1877,1882,1887,1892],{"__ignoreMap":110},[114,1829,1830],{"class":116,"line":117},[114,1831,120],{},[114,1833,1834],{"class":116,"line":123},[114,1835,1836],{},"    A[Scénarios Cucumber de StockService] --> B[Tests par sous-contexte]\n",[114,1838,1839],{"class":116,"line":129},[114,1840,1841],{},"    B --> C1[Réservation]\n",[114,1843,1844],{"class":116,"line":135},[114,1845,1846],{},"    B --> C2[Réapprovisionnement]\n",[114,1848,1849],{"class":116,"line":141},[114,1850,1851],{},"    B --> C3[Inventaire]\n",[114,1853,1854],{"class":116,"line":147},[114,1855,1856],{},"    C1 --> D1[fingerprint.pb]\n",[114,1858,1859],{"class":116,"line":153},[114,1860,1861],{},"    C2 --> D2[fingerprint.pb]\n",[114,1863,1864],{"class":116,"line":159},[114,1865,1866],{},"    C3 --> D3[fingerprint.pb]\n",[114,1868,1869],{"class":116,"line":165},[114,1870,1871],{},"    D1 --> E[Regroupement]\n",[114,1873,1874],{"class":116,"line":171},[114,1875,1876],{},"    D2 --> E\n",[114,1878,1879],{"class":116,"line":177},[114,1880,1881],{},"    D3 --> E\n",[114,1883,1884],{"class":116,"line":183},[114,1885,1886],{},"    E --> F[fingerprints.tar.gz]\n",[114,1888,1889],{"class":116,"line":189},[114,1890,1891],{},"    F --> G[Prérelease]\n",[114,1893,1894],{"class":116,"line":430},[114,1895,1896],{},"    G --> H[Release]\n",[19,1898,1899,1900,1902,1903,1179,1905,1907,1908,1910],{},"Dans une architecture microservices, cette chaîne existe indépendamment dans chaque dépôt. ",[67,1901,69],{},",",[67,1904,75],{},[67,1906,81],{}," et ",[67,1909,87],{}," exécutent leurs propres scénarios, construisent leur propre bundle d’empreintes et le publient avec leur propre release. La mise en relation intervient ensuite, à partir de ces artefacts déjà produits et versionnés :",[105,1912,1914],{"className":107,"code":1913,"language":109,"meta":110,"style":110},"flowchart TB\n    subgraph ORDER[OrderService]\n        direction LR\n        O1[Scénarios Order] --> O2[Tests de maillon] --> O3[fingerprints.tar.gz] --> O4[Release Order]\n    end\n\n    subgraph STOCK[StockService]\n        direction LR\n        S1[Scénarios Stock] --> S2[Tests de maillon] --> S3[fingerprints.tar.gz] --> S4[Release Stock]\n    end\n\n    subgraph PRODUCTION[ProductionService]\n        direction LR\n        P1[Scénarios Production] --> P2[Tests de maillon] --> P3[fingerprints.tar.gz] --> P4[Release Production]\n    end\n\n    subgraph NOTIFICATION[NotificationService]\n        direction LR\n        N1[Scénarios Notification] --> N2[Tests de maillon] --> N3[fingerprints.tar.gz] --> N4[Release Notification]\n    end\n\n    O4 --> M[Mise en relation des empreintes versionnées]\n    S4 --> M\n    P4 --> M\n    N4 --> M\n    M --> V[Vue globale des interactions observées]\n",[67,1915,1916,1921,1926,1931,1936,1941,1945,1950,1954,1959,1963,1967,1972,1976,1981,1985,1989,1994,1998,2003,2007,2011,2016,2021,2026,2031],{"__ignoreMap":110},[114,1917,1918],{"class":116,"line":117},[114,1919,1920],{},"flowchart TB\n",[114,1922,1923],{"class":116,"line":123},[114,1924,1925],{},"    subgraph ORDER[OrderService]\n",[114,1927,1928],{"class":116,"line":129},[114,1929,1930],{},"        direction LR\n",[114,1932,1933],{"class":116,"line":135},[114,1934,1935],{},"        O1[Scénarios Order] --> O2[Tests de maillon] --> O3[fingerprints.tar.gz] --> O4[Release Order]\n",[114,1937,1938],{"class":116,"line":141},[114,1939,1940],{},"    end\n",[114,1942,1943],{"class":116,"line":147},[114,1944,320],{"emptyLinePlaceholder":319},[114,1946,1947],{"class":116,"line":153},[114,1948,1949],{},"    subgraph STOCK[StockService]\n",[114,1951,1952],{"class":116,"line":159},[114,1953,1930],{},[114,1955,1956],{"class":116,"line":165},[114,1957,1958],{},"        S1[Scénarios Stock] --> S2[Tests de maillon] --> S3[fingerprints.tar.gz] --> S4[Release Stock]\n",[114,1960,1961],{"class":116,"line":171},[114,1962,1940],{},[114,1964,1965],{"class":116,"line":177},[114,1966,320],{"emptyLinePlaceholder":319},[114,1968,1969],{"class":116,"line":183},[114,1970,1971],{},"    subgraph PRODUCTION[ProductionService]\n",[114,1973,1974],{"class":116,"line":189},[114,1975,1930],{},[114,1977,1978],{"class":116,"line":430},[114,1979,1980],{},"        P1[Scénarios Production] --> P2[Tests de maillon] --> P3[fingerprints.tar.gz] --> P4[Release Production]\n",[114,1982,1983],{"class":116,"line":436},[114,1984,1940],{},[114,1986,1987],{"class":116,"line":442},[114,1988,320],{"emptyLinePlaceholder":319},[114,1990,1991],{"class":116,"line":448},[114,1992,1993],{},"    subgraph NOTIFICATION[NotificationService]\n",[114,1995,1996],{"class":116,"line":454},[114,1997,1930],{},[114,1999,2000],{"class":116,"line":460},[114,2001,2002],{},"        N1[Scénarios Notification] --> N2[Tests de maillon] --> N3[fingerprints.tar.gz] --> N4[Release Notification]\n",[114,2004,2005],{"class":116,"line":466},[114,2006,1940],{},[114,2008,2009],{"class":116,"line":472},[114,2010,320],{"emptyLinePlaceholder":319},[114,2012,2013],{"class":116,"line":478},[114,2014,2015],{},"    O4 --> M[Mise en relation des empreintes versionnées]\n",[114,2017,2018],{"class":116,"line":484},[114,2019,2020],{},"    S4 --> M\n",[114,2022,2023],{"class":116,"line":490},[114,2024,2025],{},"    P4 --> M\n",[114,2027,2028],{"class":116,"line":780},[114,2029,2030],{},"    N4 --> M\n",[114,2032,2033],{"class":116,"line":786},[114,2034,2035],{},"    M --> V[Vue globale des interactions observées]\n",[19,2037,2038,2039,2042,2043,2045,2046,29],{},"Il n’existe donc pas une release technique commune qui fabriquerait l’empreinte de toute la boulangerie. Chaque service reste autonome et publie la photographie comportementale correspondant à sa version. C’est seulement après cette étape que les empreintes peuvent être rapprochées : l’appel ",[67,2040,2041],{},"StockService.ReserveIngredients"," observé chez ",[67,2044,69],{}," peut, par exemple, être mis en regard des scénarios exposant cette méthode dans la release de ",[67,2047,75],{},[19,2049,2050],{},"L’empreinte reste ainsi au plus près de la version du service qui l’a produite. Comparer deux releases d’un même service peut révéler une nouvelle dépendance, une interaction disparue ou une structure de données qui a changé. Comparer les releases de plusieurs services permet, de son côté, de rapprocher les pièces du parcours global tout en conservant leur origine et leur version.",[53,2052,2054],{"id":2053},"conclusion-de-la-connaissance-dun-atelier-à-celle-de-la-boulangerie","Conclusion : de la connaissance d’un atelier à celle de la boulangerie",[19,2056,2057],{},"Un scénario fidèle traduit la compréhension qu’une équipe possède de son métier : les situations importantes, les règles à préserver, les partenaires sollicités et les effets attendus. Dans notre boulangerie, l’équipe commande sait ce qui rend une commande acceptable ; l’équipe stock connaît les règles de réservation ; l’équipe production sait transformer cette commande en travail réalisable.",[19,2059,2060],{},"L’empreinte relie ce savoir métier aux interactions qui le mettent en œuvre. La requête SQL, le service appelé, l’événement publié ou l’objet lu dans un bucket ne sont plus des dépendances isolées : ils deviennent les conséquences observables d’un besoin compris, raconté et vérifié par l’équipe.",[19,2062,2063],{},"Des scénarios entretenus collectivement conservent ainsi le savoir dans un format lisible par les équipes et vérifiable par l’exécution. Lorsque le métier évolue, faire évoluer les scénarios maintient ensemble la compréhension fonctionnelle, la preuve technique et la cartographie des interactions.",[19,2065,2066],{},"Enfin, la somme des empreintes retrouve une problématique que les tests E2E classiques cherchent, eux aussi, à résoudre : savoir, avec le plus d’exhaustivité possible, ce qui s’est passé lorsqu’une action a traversé tout ou partie du système d’information. Là où un test de maillon éclaire précisément un atelier, la mise en relation des empreintes reconstitue progressivement le voyage de la commande entre tous les ateliers de la boulangerie.",[19,2068,2069],{},"Cette vision ne vaut que pour les parcours réellement exécutés et les interactions effectivement instrumentées. Son exhaustivité ne se décrète pas : elle se construit avec la qualité, la diversité et la représentativité des scénarios. Plus les équipes racontent fidèlement leur métier, plus la somme de leurs empreintes devient une compréhension partagée du système tout entier.",[2071,2072,2073],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sPWt5, html code.shiki .sPWt5{--shiki-default:#7EE787}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}",{"title":110,"searchDepth":123,"depth":123,"links":2075},[2076,2079,2082,2083,2090,2091],{"id":55,"depth":123,"text":56,"children":2077},[2078],{"id":199,"depth":129,"text":200},{"id":292,"depth":123,"text":293,"children":2080},[2081],{"id":507,"depth":129,"text":508},{"id":540,"depth":123,"text":541},{"id":876,"depth":123,"text":2084,"children":2085},"Dans les coulisses : comment ScenarioFlow construit l’empreinte",[2086,2087,2088,2089],{"id":971,"depth":129,"text":972},{"id":1028,"depth":129,"text":1029},{"id":1067,"depth":129,"text":1068},{"id":1192,"depth":129,"text":1193},{"id":1784,"depth":123,"text":1785},{"id":2053,"depth":123,"text":2054},"2026-10-12T09:16:28.030Z","Il est huit heures, un samedi matin. Une cliente commande un gâteau d’anniversaire personnalisé sur le site de sa boulangerie. Elle choisit la recette, indique le nombre de parts, joint une image d’in","md",".\u002Fassets\u002Fcover-image.webp",{},"\u002Fblogs\u002F2026-10-12-cartographier-son-systeme-dinformation-sans-tout-redeployer-la-methode-des-tests-de-maillon",[2099,2106],{"id":2100,"name":2101,"image":2102,"linkedin":2103,"x":2104,"jobTitle":2105},"e8163b24-7e01-41c5-adbf-0dc655f929d0","Nicolas Zago",".\u002Fassets\u002Freviewer-nicolas-zago.webp","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolaszago\u002F",null,"CEO HoppR",{"id":2107,"name":2108,"image":2109,"linkedin":2110,"x":2104,"jobTitle":2111,"bio":2112},"188f4462-cd38-80d5-b9e6-ec28a94d11e5","Bastien Dufour",".\u002Fassets\u002Freviewer-bastien-dufour.webp","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fb-dufour\u002F","Senior Software Engineer","I use vim btw",{"title":6,"description":2093},"blogs\u002F2026-10-12-cartographier-son-systeme-dinformation-sans-tout-redeployer-la-methode-des-tests-de-maillon\u002Findex",[2116,2117,2118],"craft","architecture","microservices","Z8lcEocgP-CFRdxAimc7ci92-FyZCX6cYouP1xJ81do",1791827088726]