Chapitre 721 minIntermédiaire
Diagramme de séquence : lignes de vie, messages et fragments
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- lire et dessiner un diagramme de séquence ;
- utiliser les fragments alt, opt et loop avec leurs gardes ;
- écrire le code d'un diagramme de séquence, et l'inverse ;
- distinguer le diagramme de séquence système du diagramme détaillé.
3 exercices corrigés1 schéma
Le diagramme de séquence montre comment des objets collaborent dans le temps pour réaliser un scénario : chaque participant a une ligne de vie verticale, le temps s'écoule de haut en bas, et chaque message (un appel de méthode, une requête, un retour) est une flèche horizontale entre deux lignes de vie. Des fragments combinés (alt, opt, loop) ajoutent les conditions et les boucles. C'est le diagramme qui se rapproche le plus du code. Ce chapitre présente sa notation, la différence entre le diagramme de séquence système et le diagramme détaillé, et le passage du diagramme au code et inversement.
Les lignes de vie
Chaque participant est représenté en haut du diagramme, avec une ligne de vie pointillée qui descend :
- un acteur (le bonhomme du diagramme de cas d'utilisation), qui déclenche le scénario ;
- un objet, dans un rectangle, nommé
nom : Classe(fiche : FicheFrais),: Classequand le nom n'importe pas (:ControleurValidation), ou simplementnom. En UML 2, le nom n'est plus souligné, contrairement à l'usage d'UML 1 qu'on voit encore dans certains sujets.
On place en général l'acteur à gauche, puis les objets dans l'ordre où ils interviennent : l'interface, le contrôleur, les objets métier, l'accès aux données. Le diagramme se lit alors de gauche à droite et de haut en bas.
Les messages
| Message | Notation | Sens | En code |
|---|---|---|---|
| Synchrone | Trait plein, pointe pleine | L'émetteur attend la fin du traitement | Un appel de méthode ordinaire |
| Asynchrone | Trait plein, pointe ouverte | L'émetteur continue sans attendre | Un événement, un message dans une file, un appel non attendu |
| Retour | Trait pointillé, pointe ouverte | La réponse à un message synchrone, avec la valeur renvoyée | Le return |
| Création | Pointillé vers la tête d'une nouvelle ligne de vie, avec «create» | L'objet naît à ce moment | new FicheFrais(…) |
| Destruction | Une croix en bas de la ligne de vie | L'objet disparaît | La fin de la composition, un Dispose() |
| Réflexif | Une flèche qui revient sur la même ligne de vie | L'objet s'appelle lui-même | this.Total() |
Un message porte le nom de l'opération appelée et ses arguments : Valider(ines), Lire(12). Il peut être numéroté (1, 2, 3…) pour faciliter les explications. Un retour porte la valeur renvoyée (fiche, 2847.34) ; on peut l'omettre quand il n'apporte rien.
La barre d'activation (un rectangle fin posé sur la ligne de vie) montre la période pendant laquelle l'objet travaille : elle commence quand il reçoit un message et se termine quand il rend la main. Elle rend visibles les appels imbriqués ; sur une copie, tu peux l'omettre si le sujet ne la demande pas.
Les fragments combinés
Un fragment combiné est un cadre qui entoure une partie des messages, avec un opérateur dans son coin supérieur gauche et une ou plusieurs gardes entre crochets.
| Opérateur | Rôle | Équivalent en code |
|---|---|---|
alt | Alternative : plusieurs opérandes séparés par un pointillé, chacun avec sa garde ; un seul s'exécute | if … else if … else |
opt | Option : un seul opérande, exécuté si la garde est vraie | if sans else |
loop | Boucle : l'opérande se répète tant que la garde est vraie, ou pour chaque élément | for, foreach, while |
break | Interruption : si la garde est vraie, l'opérande s'exécute et le reste du fragment englobant est abandonné | break, ou un return anticipé |
par | Parallèle : les opérandes s'exécutent en même temps | Des tâches lancées en parallèle (Task.WhenAll, Promise.all) |
ref | Référence : renvoie à un autre diagramme de séquence, nommé dans le cadre | L'appel d'une méthode détaillée ailleurs |
critical | Section critique : rien ne peut s'intercaler | Un verrou (lock), une transaction |
La garde d'une boucle peut donner un nombre d'itérations : loop(1, 3) s'exécute de une à trois fois (trois tentatives de connexion, par exemple). Plus souvent, on écrit la condition ou le parcours en français : [pour chaque ligne hors forfait], [tant que des fiches restent à valider].
Le scénario « Valider une fiche de frais »
Voici le scénario nominal et l'erreur principale du cas « Valider une fiche de frais » (chapitre 3), dans l'application de bureau des comptables. Elle suit une architecture en couches : une vue affiche l'écran, un contrôleur reçoit les actions de l'utilisateur et orchestre le traitement, un objet DAO (Data Access Object) lit et enregistre les fiches dans la base (Repository et DAO).
Lis-le de haut en bas :
- le comptable clique sur « Valider » ;
- la vue transmet la demande au contrôleur, avec le numéro de la fiche (12) et le comptable connecté ;
- le contrôleur demande la fiche au DAO, qui la lit dans la base et, s'il la trouve, crée l'objet
FicheFraiscorrespondant (message 4, dans le fragmentopt), puis le renvoie (5) ; s'il ne la trouve pas, il renvoienull; - le fragment
altsépare deux cas : si la fiche n'existe pas ou n'est pas clôturée, le contrôleur affiche une erreur (6) ; sinon, il demande à la fiche de se valider (7), elle calcule son total (8), le contrôleur la fait enregistrer (9) et affiche la confirmation (10).
Remarque la répartition des rôles : la règle (« on ne valide qu'une fiche clôturée », « le montant validé est le total ») est dans la classe métier FicheFrais ; le contrôleur ne fait qu'enchaîner les appels. Le diagramme de séquence rend ce choix de conception visible d'un coup d'œil.
Du diagramme au code
Le diagramme de séquence se traduit presque ligne à ligne : chaque message partant d'une ligne de vie devient une instruction dans la méthode en cours d'exécution, le fragment alt devient un if … else, un loop deviendrait un foreach.
public interface IFicheDao
{
FicheFrais? Lire(int id);
void Enregistrer(FicheFrais fiche);
}
public interface IVueValidation
{
void AfficherErreur(string message);
void AfficherConfirmation(decimal montant);
}
public class ControleurValidation
{
private readonly IFicheDao dao;
private readonly IVueValidation vue;
public ControleurValidation(IFicheDao dao, IVueValidation vue)
{
this.dao = dao;
this.vue = vue;
}
// Message 2 : Valider(12, ines)
public void Valider(int idFiche, Comptable comptable)
{
FicheFrais? fiche = dao.Lire(idFiche); // messages 3 à 5
if (fiche is null || fiche.Etat != EtatFiche.Cloturee) // alt, premier opérande
{
vue.AfficherErreur("Cette fiche n'est pas à valider."); // message 6
return;
}
fiche.Valider(comptable); // messages 7 et 8
dao.Enregistrer(fiche); // message 9
vue.AfficherConfirmation(fiche.MontantValide ?? 0); // message 10
}
}
Les interfaces IFicheDao et IVueValidation permettent de tester le contrôleur sans base de données ni écran : un test unitaire lui passe un faux DAO qui renvoie une fiche préparée à l'avance, et une fausse vue qui enregistre ce qu'on lui demande d'afficher (mock, stub et spy). Sur la ligne de vie, on écrit :FicheDao, la classe concrète utilisée à l'exécution, ou :IFicheDao si l'on veut insister sur le contrat.
Du code au diagramme
Le chemin inverse sert à documenter un code existant. Prends la méthode que tu veux expliquer, crée une ligne de vie par objet qu'elle utilise, et dessine une flèche par appel, dans l'ordre d'exécution. Voici TotalHorsForfait() écrite avec une boucle, une version équivalente à celle du chapitre 6, qui utilise LINQ :
public decimal TotalHorsForfait()
{
decimal total = 0;
foreach (LigneFraisHorsForfait ligne in lignesHorsForfait)
{
if (!ligne.Refuse) total += ligne.Montant;
}
return total;
}
on obtient deux lignes de vie (fiche : FicheFrais et ligne : LigneFraisHorsForfait), un fragment loop [pour chaque ligne hors forfait] qui contient un fragment opt [ligne non refusée] avec le message Montant envoyé à la ligne, puis le retour du total. Écrit avec PlantUML, un outil qui dessine les diagrammes à partir de texte (chapitre 11) :
@startuml
participant "fiche : FicheFrais" as fiche
participant "ligne : LigneFraisHorsForfait" as ligne
[-> fiche : TotalHorsForfait()
loop pour chaque ligne hors forfait
fiche -> ligne : Refuse
ligne --> fiche : refusé ?
opt ligne non refusée
fiche -> ligne : Montant
ligne --> fiche : montant
end
end
[<-- fiche : total
@enduml
On ne dessine pas chaque lecture de propriété dans un vrai diagramme : ce niveau de détail sert à comprendre la traduction, pas à documenter. Garde les messages qui montrent une responsabilité : qui décide, qui calcule, qui enregistre.
Diagramme de séquence système et diagramme détaillé
Il existe deux niveaux de diagramme de séquence.
- Le diagramme de séquence système (DSS) n'a que deux lignes de vie : l'acteur et le système, vu comme une boîte noire. Il illustre un cas d'utilisation, étape par étape, sans rien dire de l'intérieur. On le dessine pendant l'analyse, à partir de la description textuelle.
- Le diagramme de séquence détaillé ouvre la boîte : les lignes de vie sont les objets (vues, contrôleurs, classes métier, DAO). On le dessine pendant la conception ; c'est celui de la validation ci-dessus.
Le DSS de « Valider une fiche de frais » :
@startuml
actor Comptable
participant AppliFrais
Comptable -> AppliFrais : ouvrir l'écran de validation
AppliFrais --> Comptable : visiteurs ayant une fiche clôturée
Comptable -> AppliFrais : choisir un visiteur
AppliFrais --> Comptable : détail de la fiche et total
opt frais hors forfait non conforme
Comptable -> AppliFrais : refuser le frais (motif)
AppliFrais --> Comptable : total recalculé
end
Comptable -> AppliFrais : confirmer la validation
AppliFrais --> Comptable : confirmation et montant validé
@enduml
Séquence d'un appel d'API
Le diagramme de séquence est aussi l'outil naturel pour décrire les échanges entre applications : un front React qui appelle une API Node.js, qui interroge la base MySQL. Les lignes de vie sont alors des programmes (le navigateur, l'API, la base) plutôt que des objets, et les messages des requêtes HTTP ou SQL : GET /v1/fiches?etat=CL, SELECT … FROM fiche_frais, réponse 200 OK. Les échanges d'authentification par jeton, la vérification CORS ou les webhooks se lisent bien mieux ainsi qu'en texte : le cours API REST en donne plusieurs exemples, dont la connexion et l'envoi du jeton.
Pour ces échanges, distingue bien les messages synchrones (le front attend la réponse HTTP, même si le code JavaScript utilise await) et les messages asynchrones (un webhook envoyé sans attendre de réponse utile, un message déposé dans une file).
Les erreurs classiques
| Erreur | Correction |
|---|---|
| Un message vers un objet dont la classe n'a pas l'opération | Ajouter l'opération au diagramme de classes, ou corriger le message |
| Des lignes de vie pour des classes au lieu d'objets (« FicheFrais » sans les deux-points) | fiche : FicheFrais ou :FicheFrais |
| L'acteur qui envoie des messages directement au DAO ou à la base | L'acteur ne parle qu'à l'interface ; la vue transmet au contrôleur |
Un if écrit en toutes lettres sur une flèche | Un fragment alt ou opt avec sa garde |
| Des retours pour chaque message, même vides | Ne garder que les retours qui renvoient une valeur utile |
| Un diagramme de 40 messages | Un diagramme par scénario ; ref pour réutiliser une partie |
Exercices
Exercice 1 sur 3 Lire un diagramme de séquence Réussi À revoir
Un sujet donne ce diagramme (en texte) pour la connexion à une application :
Utilisateur -> :VueConnexion : saisir(login, mdp)
:VueConnexion -> :ControleurAuth : Connecter(login, mdp)
:ControleurAuth -> :UtilisateurDao : TrouverParLogin(login)
:UtilisateurDao --> :ControleurAuth : u
alt [u existe et mot de passe vérifié]
:ControleurAuth -> :Session : Ouvrir(u)
:ControleurAuth -> :VueConnexion : AfficherAccueil(u)
else [sinon]
:ControleurAuth -> :Journal : Tracer("échec", login)
:ControleurAuth -> :VueConnexion : AfficherErreur("Identifiants incorrects")
end
- Quelles opérations doivent exister dans la classe
ControleurAuth? dansUtilisateurDao? - Pourquoi le message d'erreur ne précise-t-il pas si c'est le login ou le mot de passe qui est faux ?
- Écris la méthode
Connecteren C#.
Voir la réponseMasquer la réponse de l'exercice 1
ControleurAuth:+ Connecter(login : string, mdp : string).UtilisateurDao:+ TrouverParLogin(login : string) : Utilisateur [0..1](il peut ne rien trouver). Il faut aussiOuvrir(u)dansSession,Tracer(…)dansJournal,AfficherAccueil(u)etAfficherErreur(…)dansVueConnexion.- Pour ne pas aider un attaquant à deviner les identifiants valides : un message différent pour « login inconnu » permettrait de tester des listes de logins. C'est une bonne pratique de sécurité (cours Sécurité applicative).
- En supposant une méthode
VerifierMotDePasse(mdp)dansUtilisateur(le mot de passe est stocké haché) :
public void Connecter(string login, string mdp)
{
Utilisateur? u = dao.TrouverParLogin(login);
if (u is not null && u.VerifierMotDePasse(mdp))
{
session.Ouvrir(u);
vue.AfficherAccueil(u);
}
else
{
journal.Tracer("échec", login);
vue.AfficherErreur("Identifiants incorrects");
}
}
La vérification du mot de passe n'apparaît pas dans le diagramme : elle est dans la garde. Si elle se faisait par un appel à un autre objet (un service de hachage), il faudrait le dessiner comme un message.
Tu avais trouvé ?
Exercice 2 sur 3 Dessiner un diagramme à partir du code Réussi À revoir
Dessine (en texte, comme ci-dessus) le diagramme de séquence de la méthode suivante, appelée par le planificateur le 1er de chaque mois. L'interface IFicheDao a reçu une opération de plus, ListerParEtat(mois, etat), qui renvoie les fiches d'un mois dans un état donné.
public class ServiceCloture
{
private readonly IFicheDao dao;
private readonly IMessagerie messagerie;
public ServiceCloture(IFicheDao dao, IMessagerie messagerie)
{
this.dao = dao;
this.messagerie = messagerie;
}
public int CloturerMois(string mois)
{
int nb = 0;
foreach (FicheFrais fiche in dao.ListerParEtat(mois, EtatFiche.Creee))
{
fiche.Cloturer();
dao.Enregistrer(fiche);
nb++;
}
messagerie.EnvoyerAuxComptables($"Les fiches de {mois} sont à valider (clôturées automatiquement : {nb})");
return nb;
}
}
Voir la réponseMasquer la réponse de l'exercice 2
Planificateur -> :ServiceCloture : CloturerMois("2026-10")
:ServiceCloture -> :FicheDao : ListerParEtat("2026-10", Creee)
:FicheDao --> :ServiceCloture : fiches
loop [pour chaque fiche de la liste]
:ServiceCloture -> fiche : FicheFrais : Cloturer()
:ServiceCloture -> :FicheDao : Enregistrer(fiche)
end
:ServiceCloture ->> :Messagerie : EnvoyerAuxComptables(message)
:ServiceCloture --> Planificateur : nb
Le foreach devient un fragment loop. Le compteur nb++ est un calcul local : pas de message. L'envoi du courriel peut se dessiner en asynchrone (pointe ouverte, ->>) : le service n'attend pas que les comptables lisent le message. L'acteur est le planificateur, celui du cas « Clôturer les fiches du mois ».
Tu avais trouvé ?
Exercice 3 sur 3 Trouver les incohérences Réussi À revoir
Un étudiant présente ce diagramme de séquence pour le cas « Saisir un frais hors forfait » : l'acteur Visiteur envoie Inserer(date, libelle, montant) à :FicheDao ; :FicheDao envoie AjouterHorsForfait(date, libelle, montant) à fiche : FicheFrais ; un message « si la fiche est clôturée, afficher une erreur » part de fiche vers l'acteur. Relève les problèmes.
Voir la réponseMasquer la réponse de l'exercice 3
- L'acteur s'adresse directement au DAO : il doit passer par l'interface (une vue), qui transmet au contrôleur.
- C'est le DAO qui pilote la logique métier ; c'est le rôle du contrôleur. Le DAO ne fait que lire et enregistrer.
- La condition est écrite en toutes lettres sur une flèche : il faut un fragment
alt [fiche clôturée]/[else], ou un retour d'erreur vers le contrôleur, qui demande à la vue d'afficher le message. - Un objet métier n'envoie pas de message à l'acteur : c'est la vue qui affiche.
Une version correcte : Visiteur → :VueSaisie : saisir le frais ; :VueSaisie → :ControleurSaisie : Ajouter(date, libelle, montant) ; :ControleurSaisie → :FicheDao : LireFicheDuMois(visiteur, mois) ; alt [fiche à l'état Creee] :ControleurSaisie → fiche : AjouterHorsForfait(…), puis → :FicheDao : Enregistrer(fiche), puis → :VueSaisie : AfficherConfirmation() ; [else] :ControleurSaisie → :VueSaisie : AfficherErreur("Fiche clôturée").
Tu avais trouvé ?