Choreo UML: Comprehensive Walkthrough
Mix.install([
{:choreo, "~> 0.8.0"},
{:kino_vizjs, "~> 0.8.0"}
])
Introduction
Choreo.UML is a software component and class modeler designed to represent UML Class Diagrams natively in Elixir. Even though Elixir is a functional language, large systems are heavily composed of structural blocks like Structs, Behaviors, and Protocols.
Choreo.UML lets you model these components directly in code, declare exact fields/attributes (with optional types and visibilities), define functions/operations, specify standard structural relationships, and visually render them beautifully to Graphviz/DOT (using 3-compartment HTML record tables) or Mermaid.js (supporting both flowchart layouts and native classDiagram blocks).
alias Choreo.UML
# Initialize a standard user and authentication architecture
uml =
UML.new()
# Add a struct representing a user database entity
|> UML.add_class(:user,
type: :struct,
label: "User Struct",
fields: [
%{name: :id, type: :integer, visibility: :public},
%{name: :email, type: :string, visibility: :private},
%{name: :hashed_password, type: :string, visibility: :private}
],
functions: [
%{name: "authenticate", arity: 2, return: :boolean, visibility: :public}
]
)
# Add a behavior representing an authentication provider contract
|> UML.add_class(:auth_provider,
type: :behavior,
label: "AuthProvider Contract",
functions: [
%{name: "verify", arity: 1, return: :ok_error, visibility: :public}
]
)
# Add an interface representing a generic caching store
|> UML.add_class(:cache,
type: :interface,
functions: [
%{name: "get", arity: 1, return: :any},
%{name: "put", arity: 2, return: :ok}
]
)
# Model relationship semantics
|> UML.add_relationship(:user, :auth_provider, type: :realizes, label: "implements")
|> UML.add_relationship(:auth_provider, :cache, type: :depends, label: "uses")
Styled Graphviz Rendering
Graphviz DOT rendering constructs high-fidelity three-compartment HTML-like tables standard in UML tools:
UML.to_dot(uml)
|> Kino.VizJS.render()
Native Mermaid.js classDiagram Rendering
You can output directly to Mermaid’s native, highly structured class diagram syntax:
# Simply pass `syntax: :class_diagram`
UML.to_mermaid(uml, syntax: :class_diagram)
|> Kino.Mermaid.new()
Component Modeling & Visibility
Every class, struct, behavior, or interface is rigorously validated via NimbleOptions to ensure structural sanity.
Fields and Functions Validation
-
Fields: Supports
:name(Required),:type(Optional), and:visibility(Optional::public(+),:private(-),:protected(#), ornil). -
Functions: Supports
:name(Required),:arity(Optional),:return(Optional), and:visibility(Optional).
# Validation will trigger a clean schema error if fields are invalid
try do
UML.new()
|> UML.add_class(:bad_module, fields: [
%{visibility: :public} # Missing name!
])
rescue
e -> e
end
Relationship Types
Relationships between software blocks map exactly to standard UML connectors:
| Relationship Type | Arrow Style | Connector Style | Intended Semantics |
|---|
:inherits |
Hollow | Solid (`– | >`) | Class inheritance or adopting a behavior. |
:realizes |
Hollow | Dashed (`.. | >`) | Adopting/implementing an Elixir protocol. |
:associates |
Solid |
Solid (-->) |
Composition (e.g. struct nesting/embedding). |
:depends |
Open |
Dashed (..>) |
Run-time dependency or function calling invocation. |
# Quick example of behavior inheritance
inheritance_uml =
UML.new()
|> UML.add_class(:base_handler, type: :class)
|> UML.add_class(:http_handler, type: :class)
|> UML.add_relationship(:http_handler, :base_handler, type: :inherits, label: "inherits_from")
UML.to_mermaid(inheritance_uml, syntax: :class_diagram)
|> Kino.Mermaid.new()
Strict Contract Validation
By default, Choreo validates behavior contracts during static analysis via Analysis.broken_contracts/1. However, if you want to actively prevent invalid relationships from being added during schema construction, you can enable the :strict_contract_validation flag upon initialization:
# This will fail because strict_contract_validation is active and user missing the verify/1 function
try do
UML.new(strict_contract_validation: true)
|> UML.add_class(:auth_provider,
type: :behavior,
functions: [%{name: "verify", arity: 1}]
)
|> UML.add_class(:provider, type: :struct)
|> UML.add_relationship(:provider, :auth_provider, type: :realizes)
rescue
e -> e
end
# This succeeds perfectly because the contract is fully satisfied
uml_ok =
UML.new(strict_contract_validation: true)
|> UML.add_class(:auth_provider,
type: :behavior,
functions: [%{name: "verify", arity: 1}]
)
|> UML.add_class(:provider,
type: :struct,
functions: [%{name: "verify", arity: 1}]
)
|> UML.add_relationship(:provider, :auth_provider, type: :realizes)
Themed Visualizations
All built-in presentation palettes are fully optimized to auto-skin each component type dynamically, applying custom matching header colors, text contrasts, borders, and line properties:
# Let's inspect the Ocean theme on our UML architecture
UML.to_dot(uml, theme: :ocean)
|> Kino.VizJS.render()
# Let's inspect the Dark theme
UML.to_dot(uml, theme: :dark)
|> Kino.VizJS.render()
Dynamic Lens Queries (Choreo.Viewable)
Since Choreo.UML natively implements the Choreo.Viewable protocol, you can use the complete lens suite (Choreo.View) to query, filter, and focus on software compartments interactively:
# Focus only on the 'user' struct and its immediate dependencies
focused_uml = Choreo.View.focus(uml, :user)
UML.to_dot(focused_uml)
|> Kino.VizJS.render()
Topological & Static Code Analysis
Choreo.UML.Analysis provides a comprehensive static analysis suite to verify system integrity, calculate coupling, and flag behavior/protocol realization issues:
1. Dependency Cycle Detection
Circular references between Elixir modules slow down compilation because they trigger massive recompilation cascades. Detect them instantly:
# Introducing a circular dependency: user -> auth_provider -> cache -> user
circular_uml =
uml
|> UML.add_relationship(:cache, :user, type: :depends, label: "references")
Choreo.UML.Analysis.cycles(circular_uml)
#=> [[:user, :auth_provider, :cache]]
2. Broken Behavior & Protocol Contracts
Flag if a struct/class claims to implement a behavior contract but forgets some methods or has arity mismatches:
# Struct 'broken_user' claims to implement 'auth_provider' but is missing the 'verify/1' function
broken_uml =
UML.new()
|> UML.add_class(:auth_provider,
type: :behavior,
functions: [%{name: "verify", arity: 1}]
)
|> UML.add_class(:broken_user, type: :struct)
|> UML.add_relationship(:broken_user, :auth_provider, type: :realizes)
Choreo.UML.Analysis.broken_contracts(broken_uml)
#=> [{:broken_user, :auth_provider, [%{name: "verify", arity: 1}]}]
3. Coupling & Stability Metrics (Robert C. Martin’s Metrics)
Evaluate Afferent Coupling ($C_a$), Efferent Coupling ($C_e$), and Instability ($I$):
Choreo.UML.Analysis.coupling_metrics(uml)
Cheat Sheet
| Task / Feature | Command |
|---|---|
| Create UML Diagram |
UML.new/0 |
| Add Struct/Behavior/Class |
UML.add_class/3 (Opts: :type, :fields, :functions, :label) |
| Add Connection Relationship |
UML.add_relationship/4 (Opts: :type, :label) |
| Render Styled DOT Graphviz |
UML.to_dot/2 (Opts: :theme, :direction, :highlighted_nodes) |
| Render Native Mermaid Class |
UML.to_mermaid/2 (Opts: syntax: :class_diagram, :direction) |
| Filter Graph via Lens focus |
Choreo.View.focus/2 |
| Detect circular cycles |
Choreo.UML.Analysis.cycles/1 |
| Verify behavior contracts |
Choreo.UML.Analysis.broken_contracts/1 |
| Compute coupling metrics |
Choreo.UML.Analysis.coupling_metrics/1 |