Sequence Diagrams
Object-Oriented Design with UML
Course 1 ยท Chapter 7 ยท Sequence Diagrams
A class diagram shows who exists; it does not show how they cooperate. Chapter 6 gave the library's classes their responsibilities, but a reader still cannot see what happens when a librarian borrows a book: who is called first, who answers, and what happens when a rule says no. A sequence diagram shows one scenario as a conversation between objects, with time running downwards. It is the diagram that turns a use case into operations on the classes.
ref fragment, which Mermaid cannot draw (I tried: parse error) and which PlantUML 1.2026.8 drew. The Python (3.14) is chapter 6's version B, run for real.
New in this chapter: a call tracer I wrote that records what the code actually does and draws it as a sequence diagram, a conformance check that compares a hand-drawn diagram with the code, and a check of the operations a sequence diagram needs against the class diagram. They are small scripts, shown in full, with their limits stated.
Not done: communication, timing and interaction-overview diagrams (the paragraph on communication diagrams is from general knowledge, with nothing drawn or run); any tool that generates sequence diagrams from other languages.
Reading the Notation
Mermaid. The numbers come from autonumber; the Loan box appears partway down because it is created during the scenario.
| Element | Looks like | Mermaid | Means |
|---|---|---|---|
| Participant, lifeline | A box with a dashed or solid line down | participant Member, actor Librarian | One object (or class, or person) taking part; time passes down the line. Name an object's role or class, not a method |
| Synchronous message | Solid line, filled head | A->>B: op(args) | A calls an operation of B and waits |
| Return | Dashed line, open head | B-->>A: value | The result comes back. Optional if obvious; useful when the value matters |
| Asynchronous message | Solid line, open head | A-)B: event | A sends and carries on without waiting (a notification, a queue) |
| Activation | A thin bar on a lifeline | A->>+B and B-->>-A | The period when B is executing because of the call |
| Self message | An arrow looping back | M->>M: check(today) | An object calling its own operation |
| Creation | A box appearing mid-diagram | create participant Loan, then a message to it | The scenario makes a new object |
| Failure | Line ending in a cross | A--xB: Refusal | An exception or refusal passed back (a common convention; not the only one) |
| Note | A yellow label | Note right of N: text | A comment |
From Use Case to Sequence Diagram
In Object-Oriented Design with UML 3 the Borrow use case treated the system as a black box: "the system checks the membership...". Chapter 6 opened the box and assigned those checks to objects. A sequence diagram is where you show which object does each step. The method:
- Pick one scenario: the main success scenario, or a single extension. One diagram, one path (fragments, below, fold a few alternatives in).
- Name the participants: the actor, then the controller that receives the request (
LoanDesk), then the objects the responsibilities landed on. - Turn each step into a message to the object that should do it, using chapter 6's reasoning (Expert, Creator, Controller). Show a return when the value is used later.
- Read the diagram out loud as the use case, and check no step was lost.
| Use case step (chapter 3) | Message in the diagram below |
|---|---|
| 2. The Librarian enters the card number and ISBN (copy barcode, after chapter 4's correction) | Librarian ->> LoanDesk: borrow(card, barcode, today) |
| (finding the member and the copy: part of how the system answers) | LoanDesk ->> Catalogue: member(card), copy(barcode) |
| 3, 4, 5. Check membership, fines, loan limit | Member ->> Member: check_can_borrow(today) |
| 6. Check this copy can be lent | Member ->> Copy: check_can_be_lent() |
| 7. Record the loan, due in 21 days | Member ->> Loan: create Loan(...) |
| 8. Tell the Librarian the due date | The two Loan returns, back to the Librarian |
"Borrow a book", main success scenario, for chapter 6's design. Drawn by hand from chapter 6's design. (Full disclosure: I had already seen the tracer's output for this scenario when I wrote it, so it is not a blind test; exercise 1 is the blind one.)
Alternatives and Failure: Fragments
A fragment is a labelled box around some messages with a condition, so one diagram can hold several paths. The ones you will use:
| Fragment | Meaning | Mermaid |
|---|---|---|
| alt | One of several branches, chosen by a condition (if / else) | alt ... else ... end |
| opt | Happens only if the condition holds (if with no else) | opt ... end |
| loop | Repeated | loop ... end |
| par | Branches happen in parallel | par ... and ... end |
| ref | "See another diagram here": a reference, to keep one diagram small | Not supported (parse error when tried); PlantUML has it |
The borrow scenario with its refusals folded in: the first alt separates "member may borrow" from the failures the member's own checks raise, the nested one separates a free copy from one already on loan.
Look at who throws. The refusal for an unavailable copy comes from Copy, not Member; the diagram draws it from the member's call because the member passes the exception up. I simplified it, and a stricter reader would add a second arrow. That is the usual trade: a diagram is a model, and every line you add makes it harder to read.
Loop, Opt and Par
A desk session: loop over books, optional receipt, then two notifications sent in parallel (asynchronously).
And ref, which only PlantUML draws. The box stands for another diagram, so this one stays short:
PlantUML. The ref box names a sub-scenario drawn elsewhere.
From Sequence Diagram to Operations
A message is a promise that the receiver has that operation. So reading a sequence diagram gives you the operations for the class diagram: LoanDesk ->> Member: borrow(copy, today) means Member needs borrow(copy, today). This is the link between the two diagrams, and it can be checked mechanically. The script reads the Mermaid sources, lists each (receiver, operation), and compares with the operations on chapter 6's class diagram for the same design (the second half of its output):
Read part 2. The borrow diagram asks for nothing the class diagram lacks. The desk session diagram asks for LoanDesk.receipt and for a Notifier that is not in the class diagram at all. Neither is a mistake yet; each is a decision waiting to be made, found by comparing two drawings. Either the class diagram gains a Notifier and a receipt operation, or the sequence diagram is showing something out of scope. Exercise 2 repeats this for a new diagram.
Does the Drawing Match the Code?
Part 1 of that output compares the drawn borrow diagram with a trace of the real code. The tracer (full source below) uses Python's sys.settrace to record each call into one of the traced classes, who made it (the self of the calling frame), and what came back, then writes a Mermaid diagram. It is a small tool and it has limits: it sees only the classes in one file, draws the outermost caller as an actor, and attributes a call to the caller's class, not to the particular object.
The refused-borrow scenario (the member has unpaid fines), as the code actually ran it:
Generated from a real run of chapter 6's library_b.py; I wrote none of these arrows. The refusal travels back up the chain: from the member, to the desk, to the librarian.
The comparison in part 1 of the output found three calls in the trace that the hand-drawn diagram omits: Member -> Member.open_loans, Member -> Loan.is_open and Member -> Standard.loan_limit. The first two are helpers, and leaving them out is a legitimate choice. The third deserves a second look: it is the call to the member's kind, the polymorphic collaboration chapter 6 built (the loan limit differs by kind of member). My drawing left out the one call that carries the design's main idea. Detail is a choice you make per audience; just notice what you are choosing to hide.
The Diagram as a Test
Once the hidden calls are agreed, the comparison can be a test: do the code's calls, filtered to the diagram's level of detail, equal the diagram's calls in the same order? The check below runs it on the real code, and on a version of the code where I made a plausible refactoring (looking the copy up before the member):
The refactoring is flagged, correctly in a literal sense and unhelpfully in a practical one: the two lookups are independent, and the order does not matter. That is the cost of a strict conformance test, so use it for sequences where order is the point (a protocol, a payment flow), not for every diagram. Be clear about what it cannot do: it checks the calls this one scenario makes, so it says nothing about the other paths, and a passing check does not make the design good.
When a Sequence Diagram Is Too Big
A faithful trace of one short desk session (three borrows and one return) is this big:
Seventy-three arrows, over 3,700 pixels tall: nobody will read it. The hand-drawn diagram has 11 arrows because its author decided what mattered. The same trace drawn at four levels of detail (the call depth kept, from the exercise 3 output):
The ways to stay readable:
- One scenario per diagram. Borrow, and return, and refusals, not a whole session.
- Choose the level. Showing only the first two levels of calls (32 arrows here) tells the story; the third level is for the person implementing the member's checks.
- Hide helpers, show collaborations. A call that carries a design decision stays; a loop's housekeeping goes.
- Use
ref(in PlantUML) to name a sub-scenario and draw it once. - Fewer participants. If five objects are not needed to make the point, leave them out and say so.
- Prefer a different diagram when the point is the set of states (chapter 8) or the shape of a workflow (activity diagram) rather than who calls whom.
Communication Diagrams, in a Paragraph
A communication diagram (called a collaboration diagram in UML 1) carries the same information as a sequence diagram but draws the objects as boxes joined by lines, with numbered messages along the lines instead of a time axis. It shows which objects are linked more clearly and the order less clearly. It is a sensible choice for a small group of objects where the links matter. I did not draw or render one for this chapter, and this paragraph is from general knowledge; in practice most teams use sequence diagrams.
Usual Mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| Drawing every call | Unreadable, and out of date within a week | One scenario; hide helpers; choose a level |
| Messages that are not operations | "Check everything" is not a method the receiver can have | Name each message as the operation the receiver will implement |
| Everything sent to one "manager" object | The anemic service of chapter 6 again, drawn as a star | If one lifeline receives every message, ask who has the data (Expert) |
| Arrows pointing the wrong way | The arrow goes from the caller to the object that has the operation, not from data to its user | Read each arrow as "A asks B to..." |
| No failure path | The diagram shows the sunny day the use case already described | Add the extensions that matter as alt |
| A diagram nobody checks against code | It describes the design as intended, not as built | Trace, or review with the code open; date the diagram |
- My own probe of Mermaid was wrong. I tested several features in one diagram, and the first run failed with "Trying to inactivate an inactive participant". I had written
M-->>-D(which deactivates M) after activating D. The error was mine, not Mermaid's, and I nearly concluded a feature was missing. I fixed the diagram and re-ran: create, async, activation, loop, opt and par all work; onlyreffails. Takeaway: test one new thing at a time, and make sure a failure is caused by the thing you are testing. - The tracer silently missed two things on its first run. It drew no creation of a
Loan(a dataclass builds its__init__withexec, so the function has no file name and my filter rejected it) and no refusal arrows (I tested whether an exception was in flight withsys.exc_info, which apparently did not report one inside a trace callback; removing that test fixed it). I only noticed because I read the generated diagram instead of trusting the count. Takeaway: a recorder that is wrong looks like a quiet program; read its output against a case where you know the answer. - A number I typed instead of computing. My first draft said the hand-drawn diagram has 8 arrows. It has 11. I had written the figure into a print statement from memory. The script now counts the lines.
- A scripted edit that corrupted its own file, for the third time. I patched
conformance.pywith a Python script containing\ninside a string that was itself inside a heredoc; the newlines became real line breaks and the file would not parse (SyntaxError: unterminated string literal). I had made the same mistake twice before, while writing the chatbot course. How to avoid it: for edits that contain escape sequences, use the editor's replace tool, and run the file immediately after any scripted edit.
Hands-On Exercises
Draw the sequence diagram for "Return a Book" (the main scenario, with a late return) in Mermaid, using chapter 6's design, from reading the code, without running it. Then check it against the real code with the tracer and say what differed, if anything.
๐ View solutionDraw "send overdue notices each night": a timer triggers the desk; for each overdue loan an email is sent to the member asynchronously and, only if the member has a mobile number, a text as well. Then check which operations the diagram needs that the class diagram lacks.
๐ View solutionTrace a desk session (three borrows, one return) and draw it at four levels of detail by limiting the call depth. Report how many arrows each has, and say which level you would put in a design document and which you would never publish.
๐ View solutionChapter 7 Quick Reference
- Participants across the top, time down;
->>call,-->>return,-)asynchronous, activation bars, self messages, creation, a cross for a failure - Method: one scenario; name actor, controller and the objects that hold the responsibilities; each step becomes a message to the object that should do it; read it aloud as the use case
- Fragments:
alt,opt,loop,parin Mermaid;refonly in PlantUML (Mermaid 12.1.0 fails on it) - Every message is an operation on the receiver: compare the diagram with the class diagram to find missing operations and missing classes
- Check a diagram against the code by tracing (
sys.settrace); a diagram can be a test, but order-strict tests flag harmless refactorings - Too big? One scenario, a chosen level of detail (here 73 arrows down to 32 at two levels), hide helpers but not design-defining calls, use
ref, fewer participants - Communication diagrams show the same facts around links rather than time (not drawn here)