Powered by AppSignal & Oban Pro

Choreo Sequence Diagrams: Walkthrough

livebooks/sequence_walkthrough.livemd

Choreo Sequence Diagrams: Walkthrough

Mix.install([
  # {:choreo, "~> 0.8"},
  {:choreo, path: Path.expand("~/repos/elixir/choreo")},
  {:kino, "~> 0.19.0"}
])

Section

> Rendering diagrams: This livebook uses Kino.Mermaid to render Mermaid sequence diagrams inline. Sequence diagrams are Mermaid’s native strength; Choreo also provides a best-effort DOT renderer for static image or PDF pipelines.


What is a Sequence Diagram?

A sequence diagram shows how participants (actors, systems, components) interact with each other over time. It is one of the most effective ways to document:

  • API call flows
  • Authentication handshakes
  • Database transactions
  • Event-driven choreography
  • Error handling and retry paths

Unlike other Choreo modules, Choreo.Sequence leans heavily into Mermaid’s native sequenceDiagram syntax. GraphViz has no native sequence-diagram concept, so the DOT renderer produces a readable, timeline-style approximation rather than a formal UML sequence layout.


Building a Basic Sequence

alias Choreo.Sequence

login =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:web, label: "Web App")
  |> Sequence.add_participant(:api, label: "API")
  |> Sequence.add_participant(:db, label: "Database")
  |> Sequence.message(:user, :web, label: "Enter credentials")
  |> Sequence.message(:web, :api, label: "POST /login")
  |> Sequence.activate(:api)
  |> Sequence.message(:api, :db, label: "SELECT user_hash")
  |> Sequence.return(:db, :api, label: "hash + salt")
  |> Sequence.message(:api, :api, label: "Verify password")
  |> Sequence.return(:api, :web, label: "200 + JWT")
  |> Sequence.deactivate(:api)
  |> Sequence.message(:web, :user, label: "Redirect to dashboard")

Kino.Mermaid.new(Sequence.to_mermaid(login))

Message Types

Choreo.Sequence supports several message styles:

Type Mermaid Arrow Use case
:sync ->> Synchronous call (default)
:async -->> Asynchronous / fire-and-forget
:return -->> Response message
:self ->> Message to self (auto-detected)
messages =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:api, label: "API")
  |> Sequence.add_participant(:queue, label: "Queue")
  |> Sequence.message(:user, :api, label: "Submit order")
  |> Sequence.async(:api, :queue, label: "Enqueue job")
  |> Sequence.return(:api, :user, label: "Order accepted")
  |> Sequence.self_message(:queue, label: "Retry on failure")

Kino.Mermaid.new(Sequence.to_mermaid(messages))

Activation Boxes

Use activate/2 and deactivate/2 to show when a participant is actively processing.

activation =
  Sequence.new()
  |> Sequence.add_actor(:client, label: "Client")
  |> Sequence.add_participant(:server, label: "Server")
  |> Sequence.add_participant(:cache, label: "Cache")
  |> Sequence.message(:client, :server, label: "GET /items/42")
  |> Sequence.activate(:server)
  |> Sequence.message(:server, :cache, label: "GET items:42")
  |> Sequence.return(:cache, :server, label: "cached JSON")
  |> Sequence.return(:server, :client, label: "200 OK")
  |> Sequence.deactivate(:server)

Kino.Mermaid.new(Sequence.to_mermaid(activation))

Notes

Add explanatory notes anywhere in the diagram.

notes =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:api, label: "API")
  |> Sequence.message(:user, :api, label: "Sign up")
  |> Sequence.note({:over, :api}, "Rate limit: 5 req/min")
  |> Sequence.return(:api, :user, label: "201 Created")
  |> Sequence.note({:right, :user}, "User receives welcome email")

Kino.Mermaid.new(Sequence.to_mermaid(notes))

Note positions:

  • {:over, :participant}
  • {:left, :participant}
  • {:right, :participant}
  • {:between, :a, :b}

Fragments: Loops, Alts, and Options

Use fragments to model control flow.

Loop

loop_example =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:api, label: "API")
  |> Sequence.fragment(:loop, "until page empty")
  |> Sequence.message(:api, :api, label: "Fetch next page")
  |> Sequence.end_fragment()
  |> Sequence.return(:api, :user, label: "All pages")

Kino.Mermaid.new(Sequence.to_mermaid(loop_example))

Alt / Else

alt_example =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:api, label: "API")
  |> Sequence.message(:user, :api, label: "GET /account")
  |> Sequence.fragment(:alt, "account exists")
  |> Sequence.return(:api, :user, label: "200 OK")
  |> Sequence.fragment(:else, "account not found")
  |> Sequence.return(:api, :user, label: "404 Not Found")
  |> Sequence.end_fragment()

Kino.Mermaid.new(Sequence.to_mermaid(alt_example))

Optional

opt_example =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:api, label: "API")
  |> Sequence.message(:user, :api, label: "Place order")
  |> Sequence.fragment(:opt, "promo code provided")
  |> Sequence.message(:api, :api, label: "Apply discount")
  |> Sequence.end_fragment()
  |> Sequence.return(:api, :user, label: "Order confirmed")

Kino.Mermaid.new(Sequence.to_mermaid(opt_example))

Complete Example: OAuth2 Authorization Code Flow

oauth =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:browser, label: "Browser")
  |> Sequence.add_participant(:client, label: "Client App")
  |> Sequence.add_participant(:auth, label: "Auth Server")
  |> Sequence.add_participant(:resource, label: "Resource Server")

  # Step 1: User initiates login
  |> Sequence.message(:user, :browser, label: "Click 'Sign in with OAuth'")
  |> Sequence.message(:browser, :client, label: "Request authorization")
  |> Sequence.message(:client, :browser, label: "Redirect to /authorize")
  |> Sequence.message(:browser, :auth, label: "GET /authorize?client_id=...")

  # Step 2: User authenticates and consents
  |> Sequence.activate(:auth)
  |> Sequence.message(:auth, :browser, label: "Render login + consent")
  |> Sequence.message(:browser, :user, label: "Prompt credentials")
  |> Sequence.message(:user, :browser, label: "Submit credentials")
  |> Sequence.message(:browser, :auth, label: "POST consent")
  |> Sequence.return(:auth, :browser, label: "Redirect with code")
  |> Sequence.deactivate(:auth)

  # Step 3: Exchange code for tokens
  |> Sequence.message(:browser, :client, label: "Callback with code")
  |> Sequence.activate(:client)
  |> Sequence.message(:client, :auth, label: "POST /token (code + secret)")
  |> Sequence.return(:auth, :client, label: "access_token + refresh_token")
  |> Sequence.deactivate(:client)

  # Step 4: Use access token
  |> Sequence.activate(:client)
  |> Sequence.message(:client, :resource, label: "GET /profile (Bearer)")
  |> Sequence.return(:resource, :client, label: "User profile")
  |> Sequence.deactivate(:client)
  |> Sequence.return(:client, :browser, label: "Render dashboard")
  |> Sequence.message(:browser, :user, label: "Show dashboard")

Kino.Mermaid.new(Sequence.to_mermaid(oauth))

Analysis

Choreo.Sequence includes analysis helpers to catch common diagram problems.

alias Choreo.Sequence.Analysis

problematic =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:api, label: "API")
  |> Sequence.add_participant(:ghost, label: "Ghost Service")
  |> Sequence.message(:user, :api)
  |> Sequence.activate(:api)

Analysis.validate(problematic)

Available checks:

  • missing_labels/1 — messages without labels
  • unknown_participants/1 — messages referencing undefined participants
  • isolated_participants/1 — participants with no messages
  • unbalanced_activations/1 — activate without deactivate, or vice versa
  • unclosed_fragments/1 — fragments opened but never ended
  • validate/1 — runs all checks and returns a list of {severity, message} tuples

DOT Fallback

If you need a static image, PDF, or GraphViz pipeline, use to_dot/2. It renders a timeline-style digraph rather than a formal sequence diagram.

Sequence.to_dot(login) |> IO.puts()

When to Use What

Goal Use
Native Markdown / GitHub / Notion Sequence.to_mermaid/2
LiveBook inline rendering Kino.Mermaid.new(...)
Static PDF / image pipeline Sequence.to_dot/2
Validate diagram quality Choreo.Sequence.Analysis

Sequence diagrams shine when you need to communicate order and responsibility. Pair them with Choreo.C4 container diagrams for a complete architectural story.