Diagramme dynamique C4 : guide avec exemples

Un diagramme de conteneurs vous dit que l'API parle à l'order service, que l'order service parle à Kafka et que le notification service lit dans Kafka. Il ne vous dit pas ce qui se passe, ni dans quel ordre, quand un client clique sur Passer la commande. Le paiement est-il encaissé avant ou après l'écriture de la ligne de commande ? L'email de confirmation attend-il l'entrepôt ? Ce sont les questions qu'on se pose en revue d'incident, et les diagrammes statiques ne peuvent pas y répondre.

C'est le rôle du diagramme dynamique C4. Il reprend des éléments que vous avez déjà dessinés et numérote les interactions entre eux pour un scénario précis. Ce guide explique ce qu'est un diagramme dynamique, en quoi il diffère d'un diagramme de séquence UML, quand il vaut la peine d'en dessiner un (moins souvent qu'on ne le pense), un exemple complet, les erreurs courantes, et comment éviter qu'il devienne obsolète quand le modèle statique change.

Si vous découvrez C4, commencez par ce qu'est le modèle C4. L'exemple ci-dessous s'appuie sur le type de diagramme présenté dans le guide du diagramme de conteneurs.

Ce qu'est un diagramme dynamique

Le diagramme dynamique est l'un des diagrammes complémentaires du modèle C4, avec le diagramme de paysage système et le diagramme de déploiement. Il ne fait pas partie des quatre niveaux principaux. Il se place à côté d'eux et leur emprunte leurs éléments.

La définition de c4model.com est courte :

  • Périmètre : « Une fonctionnalité, une story, un cas d'usage, etc. »
  • Éléments : « À vous de choisir : vous pouvez montrer des systèmes logiciels, des conteneurs ou des composants qui interagissent à l'exécution. »
  • Public : « Personnes techniques et non techniques, dans et hors de l'équipe de développement. »
  • Recommandé ? « Non, les diagrammes dynamiques doivent être utilisés avec parcimonie, pour montrer des patterns intéressants ou récurrents, ou des fonctionnalités qui nécessitent un ensemble d'interactions compliqué. »

Deux choses découlent de cette définition.

D'abord, un diagramme dynamique montre des instances de relations que vous avez déjà. Si le diagramme de conteneurs a une flèche de l'order service vers Kafka, le diagramme dynamique dit : « et à l'étape 4 du checkout, cette flèche sert à publier OrderPlaced ». Le DSL de Structurizr le rend explicite : sa documentation dit qu'avec une vue dynamique, « vous montrez des instances de relations définies dans le modèle statique », et la relation doit d'abord y exister (Structurizr DSL reference). Cette contrainte est utile. Elle empêche le diagramme dynamique d'inventer un appel que le modèle statique ne connaît pas.

Ensuite, c'est un scénario par diagramme. Pas « comment fonctionne l'order service », mais « le client passe une commande, paiement par carte, article en stock ». Le chemin d'échec a son propre diagramme, s'il vaut la peine d'être dessiné.

L'ordre est indiqué par des numéros sur les flèches. C'est toute la notation : les mêmes boîtes, les mêmes flèches, plus un numéro de séquence et une description de ce qui se passe à cette étape.

Diagramme dynamique vs diagramme de séquence

« Diagramme de séquence C4 » est une recherche fréquente, et la confusion se comprend : les deux diagrammes répondent à la même question. Le site C4 indique que le diagramme dynamique peut être dessiné dans deux styles qui portent la même information :

  • Style collaboration. Des boîtes disposées librement (en général là où elles se trouvent sur le diagramme de conteneurs) avec des flèches numérotées entre elles. C4 précise que ce style s'inspire du diagramme de communication UML, autrefois appelé diagramme de collaboration.
  • Style séquence. Les éléments en colonnes en haut, le temps qui s'écoule vers le bas, des flèches entre les lignes de vie. Cela ressemble à un diagramme de séquence UML, mais les participants sont des éléments C4.

Un diagramme dynamique en style séquence est donc une sorte de diagramme de séquence. Les vraies différences sont avec un diagramme de séquence UML classique, tiré du code :

Diagramme dynamique C4 Diagramme de séquence UML (usage typique)
Participants Systèmes, conteneurs ou composants de votre modèle C4 Objets, classes, souvent au niveau des méthodes
Ce que signifie une flèche Une utilisation d'une relation du modèle statique, avec son protocole Un message ou un appel de méthode
Niveau de détail Architectural : « publie OrderPlaced (Kafka) » Souvent l'implémentation : validate(), save(), valeurs de retour
Notation Boîtes et flèches numérotées, une légende explique ce qui est inhabituel Lignes de vie, barres d'activation, fragments combinés (alt, loop, par)
Lien avec les autres diagrammes Réutilise les éléments du diagramme de conteneurs ou de composants Généralement autonome

Utilisez le style collaboration quand la disposition spatiale a du sens, par exemple quand les lecteurs connaissent déjà le diagramme de conteneurs et que vous voulez que le flux apparaisse par-dessus. Utilisez le style séquence quand l'ordre est tout l'enjeu, qu'il y a plus de huit étapes environ, ou beaucoup d'allers-retours entre deux éléments (requête, réponse, callback). Aucun n'est plus correct ; C4 vous laisse le choix.

Si vous avez besoin de fragments alt et loop pour expliquer un scénario, c'est souvent le signe que vous décrivez un algorithme plutôt qu'une architecture. Dessinez la version architecturale en diagramme dynamique, et laissez la version détaillée à un diagramme de séquence UML à côté du code, si quelqu'un en a besoin. Notre comparaison C4 vs UML indique où chaque notation a sa place.

Quand en dessiner un (et quand s'en passer)

La réponse de C4 à « recommandé ? » est non, et cela mérite d'être pris au sérieux. Chaque diagramme dynamique est un artefact de plus qui doit changer quand l'architecture change. Dessinez-en un quand le scénario remplit au moins un de ces critères :

  • L'ordre n'est pas évident à partir du diagramme statique. Checkout, capture de paiement, une saga qui compense en cas d'échec. Si un ingénieur senior de l'équipe se tromperait sur l'ordre, dessinez-le.
  • Le scénario traverse plusieurs conteneurs ou systèmes. Tout ce qui touche quatre conteneurs ou plus, ou sort de votre système puis y revient (webhooks, callbacks, redirections vers des tiers comme 3-D Secure).
  • Il est asynchrone. Dès qu'une file d'attente intervient, le diagramme statique montre que A et B touchent tous deux Kafka, mais pas que B s'exécute après A, ni que A ne l'attend pas.
  • Il est récurrent. Un pattern utilisé à de nombreux endroits (comment chaque service authentifie une requête, comment chaque écriture émet un événement) mérite un diagramme vers lequel le reste de la doc peut pointer.
  • Quelqu'un le demande en revue ou lors d'un incident. C'est le meilleur déclencheur. Si une revue d'incident a passé vingt minutes à reconstituer une séquence au tableau blanc, cette séquence mérite un diagramme.

Passez-vous-en quand :

  • Le flux est une ligne droite. Navigateur, API, base de données, retour. Le diagramme de conteneurs le dit déjà.
  • C'est du CRUD. Cinq diagrammes dynamiques pour create, read, update, delete et list n'apportent rien.
  • Personne ne le lira. Un diagramme dynamique par user story, c'est un backlog de documentation, pas de la documentation.

Une cible raisonnable pour un produit typique est une poignée : les deux ou trois parcours qui rapportent de l'argent ou réveillent les gens la nuit, plus un ou deux patterns récurrents.

Exemple complet : « le client passe une commande »

Prenons le système e-commerce de notre guide complet. Son diagramme de conteneurs comprend une single-page app React, une API gateway Kong, des services Go pour les commandes, les produits et les utilisateurs (chacun avec sa propre base PostgreSQL), Kafka et un notification service. Au niveau 1, le système parle aussi à Stripe comme passerelle de paiement et à SendGrid pour les emails.

Voici les relations du modèle statique que ce scénario utilise. Chaque étape ci-dessous doit correspondre à l'une d'elles.

[Client] --> [Single-Page Application (React)] : Utilise (HTTPS)
[Single-Page Application] --> [API Gateway (Kong)] : Appelle l'API (HTTPS/JSON)
[API Gateway] --> [Order Service (Go)] : Route les requêtes
[Order Service] --> [Product Service (Go)] : Vérifie le stock (gRPC)
[Order Service] --> [Payment Gateway (Stripe)] : Autorise les paiements (HTTPS/REST)
[Order Service] --> [Order Database (PostgreSQL)] : Lit/écrit les commandes (SQL)
[Order Service] --> [Message Queue (Kafka)] : Publie les événements de commande
[Notification Service (Go)] --> [Message Queue] : Consomme les événements de commande
[Notification Service] --> [Email Service (SendGrid)] : Envoie les emails (HTTPS)

Le diagramme dynamique, style collaboration

Les interactions numérotées, dessinées sur ces mêmes boîtes :

1.  [Client] -> [Single-Page Application] : Clique sur "Passer la commande"
2.  [Single-Page Application] -> [API Gateway] : POST /orders (HTTPS/JSON)
3.  [API Gateway] -> [Order Service] : Route la requête authentifiée
4.  [Order Service] -> [Product Service] : Réserve le stock pour chaque ligne (gRPC)
5.  [Order Service] -> [Payment Gateway (Stripe)] : Autorise la carte pour le total de la commande (HTTPS)
6.  [Order Service] -> [Order Database] : Écrit la commande avec le statut "placed" (SQL)
7.  [Order Service] -> [Message Queue] : Publie OrderPlaced (Kafka)
8.  [Order Service] -> [Single-Page Application] : Renvoie 201 avec le numéro de commande (via la gateway)
9.  [Notification Service] -> [Message Queue] : Consomme OrderPlaced (Kafka)
10. [Notification Service] -> [Email Service (SendGrid)] : Envoie l'email de confirmation (HTTPS)

Disposés sur le diagramme de conteneurs, les numéros racontent l'histoire : les étapes 1 à 8 sont synchrones et ont lieu pendant que le client attend, les étapes 9 et 10 ont lieu après, et le client ne les attend jamais.

Le même scénario, style séquence

N° De Vers Ce qui se passe Synchrone ?
1 Client Single-Page Application Clique sur « Passer la commande » oui
2 Single-Page Application API Gateway POST /orders oui
3 API Gateway Order Service Route la requête oui
4 Order Service Product Service Réserve le stock oui
5 Order Service Payment Gateway (Stripe) Autorise la carte oui
6 Order Service Order Database Écrit la commande oui
7 Order Service Message Queue Publie OrderPlaced non (fire and forget)
8 Order Service Single-Page Application Renvoie 201 avec le numéro de commande oui
9 Notification Service Message Queue Consomme OrderPlaced asynchrone
10 Notification Service Email Service (SendGrid) Envoie la confirmation asynchrone

Un tableau comme celui-ci est une façon tout à fait valable d'écrire un diagramme dynamique. Dessiné avec des lignes de vie, c'est le style séquence.

Ce que le diagramme vous apprend

En lisant les dix étapes, vous pouvez répondre à des questions auxquelles le diagramme de conteneurs ne pouvait pas répondre :

  • Que se passe-t-il si Stripe est en panne ? Le stock est déjà réservé à l'étape 4 quand l'autorisation échoue à l'étape 5. Quelqu'un doit le libérer. Le diagramme rend évident que l'order service a besoin d'un chemin de compensation, ou que les étapes 4 et 5 devraient être inversées.
  • Le client peut-il recevoir une confirmation pour une commande qui n'existe pas ? Non. L'événement est publié à l'étape 7, après l'écriture de l'étape 6. Si c'était l'inverse, une écriture ratée pourrait quand même déclencher un email. (S'il faut que l'écriture et la publication soient atomiques, c'est là qu'intervient une table outbox, et cela mérite un ADR.)
  • Qu'y a-t-il sur le chemin critique du client ? Les étapes 2 à 8. Pas l'email, c'est pourquoi il passe par Kafka.

Voici le même scénario en DSL Structurizr, pour les équipes qui gardent leur modèle sous forme de code. Il ne compile que si chaque relation existe dans le modèle statique, c'est-à-dire la contrainte décrite plus haut :

dynamic webshop "PlaceOrder" "Customer places an order" {
    customer -> spa "Clicks Place order"
    spa -> gateway "POST /orders"
    gateway -> orderService "Routes the request"
    orderService -> productService "Reserves stock"
    orderService -> stripe "Authorizes the card"
    orderService -> orderDb "Writes the order"
    orderService -> kafka "Publishes OrderPlaced"
    notificationService -> kafka "Consumes OrderPlaced"
    notificationService -> sendgrid "Sends confirmation"
    autoLayout lr
}

L'étape 8, la réponse, n'est pas une relation distincte dans le modèle statique, elle est donc absente de la version DSL. Les réponses sont généralement implicites dans la requête ; ne les dessinez que lorsque la réponse elle-même compte.

Erreurs courantes

Trop d'étapes

Un diagramme dynamique avec trente flèches numérotées est une séquence que personne ne peut garder en tête. Si un scénario dépasse une quinzaine d'étapes, découpez-le : « checkout, jusqu'au paiement » et « checkout, après le paiement », ou un diagramme par système traversé. Notre propre documentation des flows recommande 5 à 15 étapes par flow pour la même raison.

Mélanger les niveaux

C4 vous laisse choisir le niveau (systèmes, conteneurs ou composants), mais choisissez-en un par diagramme. Un diagramme où l'étape 3 va vers le conteneur « Order Service » et l'étape 4 vers le composant PaymentClient qu'il contient oblige le lecteur à changer de zoom en pleine histoire. Si une étape demande le détail des composants, dessinez un second diagramme dynamique limité à ce conteneur.

Des flèches qui n'existent pas dans le modèle statique

Si le diagramme dynamique montre le notification service appelant directement l'order service, et que le diagramme de conteneurs n'a pas cette relation, l'un des deux est faux. En général, c'est le diagramme dynamique, dessiné de mémoire. Traitez le modèle statique comme la source de vérité et faites en sorte que chaque étape référence l'une de ses relations.

Dessiner chaque appel

Health checks, rafraîchissements de token, envoi de logs et collecte de métriques sont bien réels, mais ce ne sont pas le scénario. Laissez de côté tout ce qui apparaîtrait dans chaque diagramme dynamique que vous dessinez. Si c'est important, cela aura une fois son propre diagramme de pattern récurrent.

Cacher l'asynchrone derrière des flèches qui ont l'air synchrones

Les étapes 9 et 10 ci-dessus ont lieu alors que le client a déjà une réponse. Si elles sont dessinées avec les mêmes flèches que les étapes 1 à 8, les lecteurs supposent que l'email part avant le chargement de la page. Marquez les étapes asynchrones (ligne pointillée, libellé « async », ou numérotation séparée comme 9a) et précisez la convention dans la légende.

Omettre l'échec qui compte

Un diagramme du chemin nominal est le bon choix par défaut. Mais si la raison pour laquelle vous dessinez le flux est « que se passe-t-il quand le paiement échoue », dessinez ce chemin-là, pas le chemin nominal.

Rester juste quand le modèle statique change

Un diagramme dynamique dépend deux fois du modèle statique : de ses éléments et de ses relations. C'est donc l'une des premières choses à devenir obsolète. Quelqu'un renomme l'order service en « checkout service », remplace Kafka par SQS, ou déplace la réservation de stock dans un nouveau service d'inventaire, et chaque diagramme dynamique qui touchait ces boîtes est désormais faux. Rien ne vous le signale.

Trois habitudes aident :

  1. Dessinez à partir du modèle, pas à côté. Un diagramme dynamique dans un outil de dessin est une copie du diagramme de conteneurs, et les copies dérivent. Une vue dynamique qui référence les éléments du modèle par identifiant (le DSL Structurizr le fait) prend au moins en compte les renommages, et échoue bruyamment quand une relation disparaît.
  2. Gardez la liste courte. Cinq diagrammes dynamiques vérifiés chaque trimestre valent mieux que trente qu'on n'ouvre jamais.
  3. Revoyez-les quand les conteneurs qu'ils touchent changent. Quand une pull request modifie un conteneur ou une relation, les diagrammes dynamiques qui l'utilisent font partie de la revue.

Comment fonctionnent les flows dans archyl

Dans archyl, un diagramme dynamique est un Flow : une liste ordonnée d'étapes, chacune avec un élément source, un élément cible, une relation et une description, rejouées étape par étape sur le diagramme (documentation des flows). Vous pouvez en construire un à la main en choisissant des relations de votre modèle, ou décrire le scénario et laisser le générateur de flows par IA rédiger les étapes à partir de votre modèle C4. Le générateur valide chaque étape par rapport au modèle avant de l'enregistrer : la source et la cible de chaque étape doivent exister, et la relation citée doit relier ces deux éléments. Une étape qui ne correspond pas est écartée plutôt que dessinée.

Deux limites, énoncées clairement parce qu'elles relèvent exactement du problème traité dans cette section :

  • Un flow conserve un instantané des éléments et relations qu'il utilise, pris au moment où une étape est ajoutée. Cela garde un flow lisible même si un élément est supprimé ensuite, mais cela signifie aussi que renommer un conteneur dans le modèle ne le renomme pas dans les flows existants. Quand le modèle change, ouvrez les flows qui le touchent et vérifiez-les.
  • Le score de dérive ne vérifie pas le comportement. Le score de dérive d'archyl vous indique si les éléments documentés existent encore dans le code. Si un appel synchrone entre deux services devient un message dans une file et que rien n'est renommé ni déplacé, le score ne change pas, et le flow non plus.

Pour le côté pratique, y compris la façon dont nous écrivons les flows comme des documents avec préconditions et gestion des erreurs, voir documenter les parcours utilisateurs.

FAQ

Le diagramme dynamique fait-il partie du modèle C4 ?

Oui, en tant que diagramme complémentaire. Les quatre niveaux principaux sont System Context, Container, Component et Code. Le modèle C4 ajoute trois diagrammes complémentaires : paysage système, dynamique et déploiement. Le diagramme dynamique réutilise les éléments des niveaux principaux et montre comment ils interagissent pour un scénario.

Quelle est la différence entre un diagramme dynamique C4 et un diagramme de séquence ?

Un diagramme dynamique C4 peut être dessiné en style collaboration (disposition libre, flèches numérotées) ou en style séquence (lignes de vie, temps qui s'écoule vers le bas). Le style séquence ressemble à un diagramme de séquence UML, mais ses participants sont des systèmes, conteneurs ou composants C4, et chaque flèche est une utilisation d'une relation du modèle statique, pas un appel de méthode.

Quel niveau un diagramme dynamique doit-il utiliser ?

Celui qui répond à la question, et un seul par diagramme. Le niveau conteneur est le plus courant, car la plupart des scénarios qui valent la peine d'être dessinés traversent plusieurs unités déployables. Utilisez le niveau système pour les flux entre systèmes et le niveau composant pour expliquer l'intérieur d'un conteneur.

Combien d'étapes un diagramme dynamique doit-il avoir ?

Il n'y a pas de limite officielle. Au-delà d'une quinzaine d'étapes, la plupart des lecteurs décrochent, alors découpez le scénario en parties ou dessinez un diagramme par système traversé.

Un diagramme dynamique C4 peut-il montrer de la messagerie asynchrone ?

Oui. Montrez la publication et la consommation comme des étapes numérotées distinctes, et rendez visible quelles étapes l'appelant attend et lesquelles il n'attend pas : ligne pointillée, libellé « async » ou schéma de numérotation séparé, expliqué dans la légende.

archyl prend-il en charge les diagrammes dynamiques C4 ?

Oui, sous forme de Flows. Chaque étape référence un élément source, un élément cible et une relation de votre modèle, et le flow est rejoué étape par étape sur le diagramme. Vous pouvez créer des flows à la main ou générer un brouillon à partir d'une description textuelle. Les flows conservent un instantané des éléments qu'ils utilisent, revoyez-les donc quand les conteneurs qu'ils touchent changent.


Envie de dessiner votre premier flow sur un modèle qui existe déjà ? Essayez archyl gratuitement et générez d'abord le modèle C4 à partir de votre code. À lire ensuite : Qu'est-ce que le modèle C4 ? Le guide complet | Guide du diagramme de conteneurs C4 | Documenter les parcours utilisateurs | Documentation des flows.