Coming in v0.9 — Production Ready

Model Complex Systems
With Elixir Code

Choreo is an Elixir visual modeling library. Write declarative, domain-specific graph definitions and render diagrams using Mermaid or Graphviz, backed by semantic static analysis.

Dataflow & Processing Pipelines
Choreo.Dataflow
alias Choreo.Dataflow

pipeline =
  Dataflow.new()
  |> Dataflow.add_source(:kafka, label: "Kafka Ingest")
  |> Dataflow.add_transform(:parser, label: "JSON Parser")
  |> Dataflow.add_transform(:enricher, label: "Enricher")
  |> Dataflow.add_sink(:postgres, label: "Postgres Sink")
  |> Dataflow.add_sink(:s3, label: "S3 Archive")
  |> Dataflow.connect(:kafka, :parser)
  |> Dataflow.connect(:parser, :enricher)
  |> Dataflow.connect(:enricher, :postgres)
  |> Dataflow.connect(:enricher, :s3)
graph TD kafka[("Kafka Ingest")] parser["JSON Parser"] enricher["Enricher"] postgres[("Postgres Sink")] s3[("S3 Archive")] style kafka fill:#3b82f6,stroke:#1d64d8,color:#fff style parser fill:#10b981,stroke:#059669,color:#fff style enricher fill:#10b981,stroke:#059669,color:#fff style postgres fill:#8b5cf6,stroke:#6d28d9,color:#fff style s3 fill:#8b5cf6,stroke:#6d28d9,color:#fff kafka --> parser parser --> enricher enricher --> postgres enricher --> s3

Visual Architectures of Any Type

Each tab shows the Elixir declaration above its rendered diagram output.

fsm_example.exs
alias Choreo.FSM

fsm =
  FSM.new()
  |> FSM.add_initial_state(:idle)
  |> FSM.add_state(:processing)
  |> FSM.add_state(:awaiting_payment)
  |> FSM.add_state(:cancelled)
  |> FSM.add_final_state(:completed)
  |> FSM.add_transition(:idle, :processing, label: "submit_order")
  |> FSM.add_transition(:processing, :awaiting_payment, label: "invoice_generated")
  |> FSM.add_transition(:awaiting_payment, :completed, label: "payment_received")
  |> FSM.add_transition(:awaiting_payment, :cancelled, label: "payment_timeout")
  |> FSM.add_transition(:processing, :cancelled, label: "cancel")

# Static analysis
FSM.unreachable_states(fsm)     # => []
FSM.dead_end_states(fsm)        # => [:cancelled]
FSM.shortest_path(fsm, :idle, :completed)
checkout_sequence.exs
alias Choreo.Sequence

seq =
  Sequence.new()
  |> Sequence.add_actor(:user, label: "User")
  |> Sequence.add_participant(:frontend, label: "Frontend")
  |> Sequence.add_participant(:api, label: "API Gateway")
  |> Sequence.add_participant(:payment, label: "Payment Service")
  |> Sequence.add_participant(:db, label: "Database")
  |> Sequence.message(:user, :frontend, label: "Click checkout")
  |> Sequence.message(:frontend, :api, label: "POST /orders")
  |> Sequence.activate(:api)
  |> Sequence.message(:api, :payment, label: "charge_card(amount)")
  |> Sequence.activate(:payment)
  |> Sequence.return(:payment, :api, label: "charge_id")
  |> Sequence.deactivate(:payment)
  |> Sequence.message(:api, :db, label: "INSERT order")
  |> Sequence.return(:db, :api, label: "order_id")
  |> Sequence.deactivate(:api)
  |> Sequence.return(:api, :frontend, label: "201 Created")
  |> Sequence.return(:frontend, :user, label: "Order confirmed ✓")
saga_workflow.exs
alias Choreo.Workflow

workflow =
  Workflow.new()
  |> Workflow.add_start(:order_received)
  |> Workflow.add_task(:validate, label: "Validate Order", timeout_ms: 1000)
  |> Workflow.add_task(:reserve_stock, label: "Reserve Stock", timeout_ms: 3000)
  |> Workflow.add_task(:charge, label: "Charge Card", timeout_ms: 5000)
  |> Workflow.add_task(:ship, label: "Ship Order", timeout_ms: 10_000)
  |> Workflow.add_compensation(:release_stock, for: :reserve_stock)
  |> Workflow.add_compensation(:refund, for: :charge)
  |> Workflow.add_end(:fulfilled)
  |> Workflow.connect(:order_received, :validate)
  |> Workflow.connect(:validate, :reserve_stock)
  |> Workflow.connect(:reserve_stock, :charge)
  |> Workflow.connect(:charge, :ship)
  |> Workflow.connect(:ship, :fulfilled)

# Analysis
Workflow.critical_path(workflow)   # latency bottlenecks
Workflow.missing_compensations(workflow)
shop_schema.exs
alias Choreo.ERD

erd =
  ERD.new()
  |> ERD.add_table(:users)
  |> ERD.add_column(:users, :id,         :uuid,    primary_key: true)
  |> ERD.add_column(:users, :email,      :string,  nullable: false)
  |> ERD.add_column(:users, :name,       :string)
  |> ERD.add_table(:orders)
  |> ERD.add_column(:orders, :id,        :uuid,    primary_key: true)
  |> ERD.add_column(:orders, :user_id,   :uuid,    foreign_key: {:users, :id})
  |> ERD.add_column(:orders, :total,     :decimal, nullable: false)
  |> ERD.add_table(:line_items)
  |> ERD.add_column(:line_items, :id,       :uuid, primary_key: true)
  |> ERD.add_column(:line_items, :order_id, :uuid, foreign_key: {:orders, :id})
  |> ERD.add_column(:line_items, :product,  :string)
  |> ERD.add_column(:line_items, :qty,      :integer)
  |> ERD.connect(:users, :orders,      label: "places",   relationship: :one_to_many)
  |> ERD.connect(:orders, :line_items, label: "contains", relationship: :one_to_many)
domain_classes.exs
alias Choreo.UML

uml =
  UML.new()
  |> UML.add_class(:account, label: "Account",
      fields: ["id: UUID", "email: String", "role: atom"],
      methods: ["authenticate/2", "deactivate/1"])
  |> UML.add_class(:profile, label: "Profile",
      fields: ["avatar_url: String", "bio: String"])
  |> UML.add_class(:session, label: "Session",
      fields: ["token: String", "expires_at: DateTime"])
  |> UML.add_class(:audit_log, label: "AuditLog",
      fields: ["action: String", "timestamp: DateTime"])
  |> UML.connect(:account, :profile,    label: "has_one",   type: :composition)
  |> UML.connect(:account, :session,    label: "has_many",  type: :aggregation)
  |> UML.connect(:account, :audit_log,  label: "generates", type: :dependency)

# Analysis
UML.circular_associations(uml)   # => []
payment_c4.exs
alias Choreo.C4

system =
  C4.new("E-Commerce Platform")
  |> C4.add_person(:customer, label: "Customer")
  |> C4.add_person(:admin, label: "Admin")
  |> C4.add_system(:shop_app, label: "Shop Application", boundary: :internal)
  |> C4.add_system(:payment_gw, label: "Payment Gateway", boundary: :external)
  |> C4.add_system(:email_svc, label: "Email Service", boundary: :external)
  |> C4.add_container(:api, label: "REST API", tech: "Phoenix/Elixir")
  |> C4.add_container(:web, label: "Web UI", tech: "LiveView")
  |> C4.add_container(:db, label: "Database", tech: "PostgreSQL")
  |> C4.connect(:customer, :web,        label: "Browses")
  |> C4.connect(:web, :api,             label: "HTTP/JSON")
  |> C4.connect(:api, :db,              label: "Ecto queries")
  |> C4.connect(:api, :payment_gw,      label: "Charge card")
  |> C4.connect(:api, :email_svc,       label: "Send receipt")
  |> C4.connect(:admin, :api,           label: "Manages")

# Analysis
C4.shared_databases(system)    # detect coupling
C4.external_interfaces(system)
elixir_mindmap.exs
alias Choreo.MindMap

map =
  MindMap.new()
  |> MindMap.set_root(:elixir, label: "Elixir")
  |> MindMap.add_topic(:concurrency, label: "Concurrency")
  |> MindMap.add_topic(:ecosystem, label: "Ecosystem")
  |> MindMap.add_topic(:tooling, label: "Tooling")
  |> MindMap.add_subtopic(:processes, label: "Processes", under: :concurrency)
  |> MindMap.add_subtopic(:genservers, label: "GenServers", under: :concurrency)
  |> MindMap.add_subtopic(:beam, label: "BEAM VM", under: :ecosystem)
  |> MindMap.add_subtopic(:hex, label: "Hex.pm", under: :ecosystem)
  |> MindMap.add_subtopic(:mix, label: "Mix", under: :tooling)
  |> MindMap.add_subtopic(:livebook, label: "Livebook", under: :tooling)
  |> MindMap.branch(:elixir, :concurrency)
  |> MindMap.branch(:elixir, :ecosystem)
  |> MindMap.branch(:elixir, :tooling)
v1_launch_plan.exs
alias Choreo.Planner

project =
  Planner.new("Launch v1.0")
  |> Planner.add_milestone(:design_complete, title: "Design Complete")
  |> Planner.add_milestone(:beta_release, title: "Beta Release")
  |> Planner.add_task(:wireframes, title: "Wireframes",   status: :done,        estimate_hours: 12)
  |> Planner.add_task(:ui_design,  title: "UI Design",   status: :done,        estimate_hours: 20)
  |> Planner.add_task(:api_impl,   title: "API Impl",    status: :in_progress, estimate_hours: 40)
  |> Planner.add_task(:frontend,   title: "Frontend",    status: :in_progress, estimate_hours: 32)
  |> Planner.add_task(:testing,    title: "QA Testing",  status: :backlog,     estimate_hours: 16)
  |> Planner.add_task(:deploy,     title: "Deploy",      status: :backlog,     estimate_hours: 8)
  |> Planner.depends_on(:frontend, :ui_design)
  |> Planner.depends_on(:api_impl, :wireframes)
  |> Planner.depends_on(:testing, :api_impl)
  |> Planner.depends_on(:testing, :frontend)
  |> Planner.depends_on(:deploy, :testing)

# Analysis
Planner.critical_path(project)     # longest dependency chain
Planner.ready_to_start(project)    # tasks with all deps met
api_threat_model.exs
alias Choreo.ThreatModel

tm =
  ThreatModel.new()
  |> ThreatModel.add_actor(:user, label: "End User")
  |> ThreatModel.add_actor(:attacker, label: "Attacker", external: true)
  |> ThreatModel.add_process(:web_server, label: "Web Server")
  |> ThreatModel.add_process(:auth_service, label: "Auth Service")
  |> ThreatModel.add_datastore(:user_db, label: "User DB",    sensitive: true)
  |> ThreatModel.add_datastore(:session_cache, label: "Redis Cache")
  |> ThreatModel.add_trust_boundary(:internet, label: "Internet Boundary")
  |> ThreatModel.add_trust_boundary(:dmz, label: "DMZ")
  |> ThreatModel.connect(:user, :web_server,          label: "HTTPS/443")
  |> ThreatModel.connect(:web_server, :auth_service,  label: "gRPC")
  |> ThreatModel.connect(:auth_service, :user_db,     label: "SQL")
  |> ThreatModel.connect(:auth_service, :session_cache, label: "SET/GET")

ThreatModel.cross_boundary_flows(tm)   # flows crossing trust zones
ThreatModel.sensitive_assets(tm)       # datastores marked sensitive
module_deps.exs
alias Choreo.Dependency

deps =
  Dependency.new()
  |> Dependency.add_module(:core,       label: "Core Business",  layer: :domain)
  |> Dependency.add_module(:accounts,   label: "Accounts",       layer: :domain)
  |> Dependency.add_module(:orders,     label: "Orders",         layer: :domain)
  |> Dependency.add_module(:auth,       label: "Auth",           layer: :application)
  |> Dependency.add_module(:repo,       label: "Repo",           layer: :infrastructure)
  |> Dependency.add_module(:mailer,     label: "Mailer",         layer: :infrastructure)
  |> Dependency.depends_on(:accounts, :core)
  |> Dependency.depends_on(:orders, :core)
  |> Dependency.depends_on(:orders, :accounts)
  |> Dependency.depends_on(:auth, :accounts)
  |> Dependency.depends_on(:repo, :core)
  |> Dependency.depends_on(:mailer, :orders)

# Analysis
Dependency.cyclic_imports(deps)       # => []
Dependency.impact_of_change(deps, :core) # all dependents
event_pipeline.exs
alias Choreo.Dataflow

pipeline =
  Dataflow.new()
  |> Dataflow.add_source(:kafka,   label: "Kafka Topic")
  |> Dataflow.add_source(:webhook, label: "Webhook")
  |> Dataflow.add_transform(:router,    label: "Event Router")
  |> Dataflow.add_transform(:parser,    label: "JSON Parser")
  |> Dataflow.add_transform(:enricher,  label: "Enricher")
  |> Dataflow.add_transform(:validator, label: "Schema Validator")
  |> Dataflow.add_sink(:postgres, label: "Postgres")
  |> Dataflow.add_sink(:s3,       label: "S3 Archive")
  |> Dataflow.add_sink(:dlq,      label: "Dead Letter Queue")
  |> Dataflow.connect(:kafka, :router)
  |> Dataflow.connect(:webhook, :router)
  |> Dataflow.connect(:router, :parser)
  |> Dataflow.connect(:parser, :enricher)
  |> Dataflow.connect(:enricher, :validator)
  |> Dataflow.connect(:validator, :postgres)
  |> Dataflow.connect(:validator, :s3)
  |> Dataflow.connect(:validator, :dlq, label: "on_error")

Dataflow.bottlenecks(pipeline)     # single-input sinks
Dataflow.strictly_acyclic?(pipeline)
pricing_decision.exs
alias Choreo.DecisionTree

tree =
  DecisionTree.new()
  |> DecisionTree.add_root(:is_premium, question: "Is user premium?")
  |> DecisionTree.add_node(:high_usage, question: "Monthly usage > 10k req?")
  |> DecisionTree.add_node(:has_discount, question: "Active discount code?")
  |> DecisionTree.add_leaf(:enterprise_price, label: "Enterprise Tier", value: 499)
  |> DecisionTree.add_leaf(:pro_price,        label: "Pro Tier",        value: 99)
  |> DecisionTree.add_leaf(:discounted,       label: "Discounted",      value: 29)
  |> DecisionTree.add_leaf(:free_tier,        label: "Free Tier",       value: 0)
  |> DecisionTree.branch(:is_premium,   true,  :high_usage)
  |> DecisionTree.branch(:is_premium,   false, :has_discount)
  |> DecisionTree.branch(:high_usage,   true,  :enterprise_price)
  |> DecisionTree.branch(:high_usage,   false, :pro_price)
  |> DecisionTree.branch(:has_discount, true,  :discounted)
  |> DecisionTree.branch(:has_discount, false, :free_tier)

# Analysis
DecisionTree.incomplete_branches(tree)  # => []
DecisionTree.reachable_leaves(tree)     # all terminal outcomes

Compose Diagrams with embed/4

Nest any Choreo diagram — Dataflow, Workflow, C4, ERD — as a labelled cluster inside a parent system. Nodes are prefixed to avoid ID collisions; edges carry their original metadata.

step 1 — build child diagrams independently
# Each child diagram is a stand-alone Choreo model
ingestion =
  Choreo.Dataflow.new()
  |> Dataflow.add_source(:kafka,    label: "Kafka")
  |> Dataflow.add_transform(:router, label: "Router")
  |> Dataflow.add_sink(:db,         label: "DB Writer")
  |> Dataflow.connect(:kafka, :router)
  |> Dataflow.connect(:router, :db)

orchestration =
  Choreo.Workflow.new()
  |> Workflow.add_start(:trigger)
  |> Workflow.add_task(:process, label: "Process")
  |> Workflow.add_end(:done)
  |> Workflow.connect(:trigger, :process)
  |> Workflow.connect(:process, :done)
step 2 — embed into a parent system
platform =
  Choreo.new()
  # Create named cluster boundaries
  |> Choreo.add_cluster("ingestion",     label: "Data Ingestion")
  |> Choreo.add_cluster("orchestration", label: "Orchestration")
  # Embed with unique prefixes — nodes become :ing_kafka, :orc_trigger …
  |> Choreo.embed(ingestion,     "ingestion",     prefix: "ing_")
  |> Choreo.embed(orchestration, "orchestration", prefix: "orc_")
  # Connect across cluster boundaries
  |> Choreo.connect(:ing_db, :orc_trigger, label: "triggers")

# Render the unified diagram
Choreo.to_mermaid(platform)
rendered output — unified cross-schema diagram

Cross-Diagram Impact Analysis

Declare semantic links between nodes in different diagram schemas. Choreo.trace/4 records a typed relationship (:reads, :writes, :triggers, …) that Choreo.Analysis.Tracing can walk transitively to answer “if this table changes, what workflows break?”

cross_schema_trace.exs
alias Choreo
alias Choreo.Analysis.Tracing

# Embed ERD schema + Workflow into one parent system
platform =
  Choreo.new()
  |> Choreo.add_cluster("schema",   label: "Data Schema")
  |> Choreo.add_cluster("checkout", label: "Checkout Workflow")
  |> Choreo.embed(schema,   "schema",   prefix: "db_")
  |> Choreo.embed(checkout, "checkout", prefix: "wf_")

# Declare semantic cross-diagram traces
platform =
  platform
  |> Choreo.trace(:wf_auth,   :db_users,  type: :reads)
  |> Choreo.trace(:wf_charge, :db_orders, type: :writes)

# Impact analysis — what is downstream of :db_users?
Tracing.impact_analysis(platform, :db_users)
# => [:wf_auth]

# Cross-diagram execution path
Tracing.trace_path(platform, :wf_auth, :db_orders)
# => {:ok, [:wf_auth, :wf_charge, :db_orders]}

# Render with trace arrows visible
Choreo.to_dot(platform, show_traces: true)
what Choreo.trace/4 does

Traces are stored as a separate edge layer — they are invisible by default so they don’t clutter normal diagrams. Pass show_traces: true to any renderer to make them appear as dashed red arrows.

Each trace carries a :type tag (:reads, :writes, :triggers, :depends_on) that the analysis engine uses to build the dependency graph.

analysis functions
Function Returns
impact_analysis/2 All nodes downstream of a change
trace_path/3 Cross-diagram execution path
to_dot/2 show_traces: Renders trace arrows in DOT / Mermaid
rendered trace diagram

Filter Any Diagram by Node Type

Choreo.View.filter/2 keeps only nodes that match a predicate. Use it to remove internals for stakeholder decks, or isolate specific node types across any diagram module.

filter_notes.exs
alias Choreo.MindMap
alias Choreo.View

map =
  MindMap.new()
  |> MindMap.set_root(:elixir, label: "Elixir")
  |> MindMap.add_topic(:concurrency, label: "Concurrency")
  |> MindMap.add_topic(:ecosystem, label: "Ecosystem")
  |> MindMap.add_subtopic(:processes, label: "Processes")
  |> MindMap.add_subtopic(:beam, label: "BEAM VM")
  |> MindMap.add_note(:history, label: "Created 2011")
  |> MindMap.branch(:elixir, :concurrency)
  |> MindMap.branch(:elixir, :ecosystem)
  |> MindMap.branch(:concurrency, :processes)
  |> MindMap.branch(:ecosystem, :beam)

# Remove all notes for a clean stakeholder deck
clean =
  View.filter(map, fn _id, data ->
    data[:node_type] != :note
  end)

MindMap.to_dot(clean)
rendered output — notes hidden

Zoom In and Out of Detail Levels

Choreo.View.zoom/2 filters diagrams by module-defined zoom levels. Each type defines what level 0, 1, 2 … means — so zooming is semantic, not just geometric.

zoom_overview.exs
alias Choreo.MindMap
alias Choreo.View

map =
  MindMap.new()
  |> MindMap.set_root(:elixir, label: "Elixir")
  |> MindMap.add_topic(:concurrency, label: "Concurrency")
  |> MindMap.add_topic(:ecosystem, label: "Ecosystem")
  |> MindMap.add_subtopic(:processes, label: "Processes")
  |> MindMap.add_subtopic(:beam, label: "BEAM VM")
  |> MindMap.add_note(:history, label: "Created 2011")
  |> MindMap.branch(:elixir, :concurrency)
  |> MindMap.branch(:elixir, :ecosystem)
  |> MindMap.branch(:concurrency, :processes)
  |> MindMap.branch(:ecosystem, :beam)

# Zoom out to show only root + topics
overview = View.zoom(map, level: 1)

MindMap.to_dot(overview)
rendered output — zoom level 1

Static Analysis Engine

Choreo acts as a query engine over domain models — detecting architectural issues without running code.

Artifact Modeling Purpose Questions Answered
C4 Model System Architecture Are system boundaries clean? Which systems share databases? What external interfaces exist?
Dataflow Processing Pipelines Where are data bottlenecks? Is backpressure possible? Are processing paths strictly acyclic?
Decision Tree Branching Heuristics Are there duplicate logic paths? Is the decision tree complete? Which branches have missing leaves?
Dependency Package & Module DAGs Are there circular package imports? Which modules are impacted if a low-level module changes?
Domain DDD Strategic & Tactical Design What are aggregate boundaries? Which events trigger policies? Are there unmapped commands?
ERD Database Schema Are column keys consistent? Are there circular entity relationships? Do we have orphaned tables?
FSM State Transitions Is the state machine deterministic? Are there unreachable or dead-end states? Shortest path to done?
Mind Map Concept Hierarchies Are there disconnected subtopics? Is the structure strictly parent-child? What is the depth distribution?
Planner Task Scheduling What is the project critical path? Which tasks are ready to start? Who has overloaded assignments?
Sequence Timeline Interactions Are activation boxes balanced? Do async returns match? Is any actor blocked indefinitely?
Threat Model STRIDE Security Which flows cross trust boundaries? Where are sensitive assets stored? What is the static risk level?
UML Class Software Types Which structs reference each other? Are there circular associations or inheritance loops?
Workflow Task Orchestration What is the latency critical path? Which tasks can run in parallel? Are Saga compensations missing?

13 Specialized Modeling DSLs

Each model targets a specific domain design need with its own semantic graph, analysis functions, and layout generator.

Architecture & Design

C4 Model

Hierarchical modeling of software systems, containers, and components following the C4 layout specification.

Choreo.C4 Systems
Behavior & Flows

Dataflow

Model dataflow processing graphs, transformation nodes, message routers, and parallel sources/sinks.

Choreo.Dataflow Pipelines
Data & Structure

Decision Tree

Evaluate complex branching heuristics and run entropy validation checks for logical soundness.

Choreo.DecisionTree Logic
Data & Structure

Dependency

Analyze acyclic package dependencies, detect cyclic loops, and optimize layering schedules.

Choreo.Dependency DAGs
Architecture & Design

Domain DDD

Map strategic bounded contexts, aggregates, entities, and event storming command-policy flows.

Choreo.Domain DDD
Architecture & Design

ERD

Database entity-relationship modeling supporting primary/foreign keys, types, and schema links.

Choreo.ERD Databases
Behavior & Flows

FSM

Model finite state machines with deterministic transitions, dead-state reachability, and path analysis.

Choreo.FSM State
Data & Structure

Mind Map

Organize concepts hierarchically into parent-child visual layouts representing domain trees.

Choreo.MindMap Trees
Data & Structure

Planner

Schedule tasks, resolve priority constraints, and isolate critical path dependencies.

Choreo.Planner PERT
Behavior & Flows

Sequence

Map sequence interactions and timelines with sync/async activations and loop/alt conditions.

Choreo.Sequence Messages
Behavior & Flows

Threat Model

Map secure boundaries, trust barriers, assets, dataflows, and calculate threat levels statically.

Choreo.ThreatModel STRIDE
Architecture & Design

UML Class

Generate object-oriented class structure layouts mapping structs, schemas, and associations.

Choreo.UML UML
Behavior & Flows

Workflow

Orchestrate tasks, parallel branches, latency weights, Saga compensation hooks, and bottlenecks.

Choreo.Workflow Sagas

Install Choreo

Add Choreo to your mix.exs and start building visual models of your domain.

def deps do
  [
    {:choreo, "~> 0.9"}
  ]
end