A domain-specific layer on top of Yog for modeling system architectures.
Choreo lets you define infrastructure diagrams (databases, caches, services, networks, etc.) as graphs and render them to DOT format with system-diagram theming. You can also run graph algorithms such as MST and topological sort to analyze your architecture.
When to use
Use Choreo when you need to document, communicate, or analyze the
infrastructure of a system — whether for architecture review, onboarding
docs, cost optimisation, or resilience planning.
Quick Start
system =
Choreo.new()
|> Choreo.add_database(:db, name: "Postgres", kind: :postgres)
|> Choreo.add_cache(:cache, name: "Redis", kind: :redis)
|> Choreo.add_service(:api, name: "API Gateway")
|> Choreo.connect(:api, :cache, cost: 5)
|> Choreo.connect(:api, :db, cost: 10)
dot = Choreo.to_dot(system)Algorithms
# Minimum spanning tree for cost optimisation
{:ok, mst} = Choreo.Analysis.mst(system)
# Topological sort for data-flow ordering
{:ok, order} = Choreo.Analysis.topological_sort(system)Diagram
Summary
Functions
Adds a cache node to the system.
Defines a cluster (subgraph) for grouping nodes visually.
Adds a database node to the system.
Adds a data-flow edge between two nodes.
Adds a load-balancer node to the system.
Adds a network / infrastructure boundary node.
Adds a generic node to the system.
Adds a queue / messaging node to the system.
Adds a service / application node to the system.
Adds a storage / blob node to the system.
Adds a user / external actor node to the system.
Returns all cluster definitions in the system.
Connects two nodes with a weighted edge.
Returns all edges in the system as a list of {from, to, cost} tuples.
Creates a new empty system diagram.
Returns all nodes in the system as a map of id => data.
Renders the system diagram to DOT format.
Returns the raw Yog.Graph struct underpinning the system.
Types
@type t() :: %Choreo{ clusters: %{required(String.t()) => map()}, edge_meta: %{optional({Yog.node_id(), Yog.node_id()}) => map()}, graph: Yog.graph() }
Functions
@spec add_cache(t(), Yog.node_id(), keyword()) :: t()
Adds a cache node to the system.
Options
:kind-:redis,:memcached, etc.:name- display name:description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_cache(:cache, kind: :redis)
iex> Choreo.nodes(system)[:cache].type
:cacheDiagram
Defines a cluster (subgraph) for grouping nodes visually.
Options
:parent- name of the parent cluster for nesting (e.g. VPC → AZ):label- display label (defaults to the cluster name):style-:filled,:rounded, etc.:fillcolor- background colour:color- border colour
Examples
iex> system = Choreo.new() |> Choreo.add_cluster("vpc", label: "VPC")
iex> Map.keys(Choreo.clusters(system))
["cluster_vpc"]
@spec add_database(t(), Yog.node_id(), keyword()) :: t()
Adds a database node to the system.
Options
:kind-:postgres,:mysql,:mongodb,:dynamodb, etc.:name- display name (defaults to the node id):description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_database(:db, kind: :postgres)
iex> Choreo.nodes(system)[:db].type
:database
iex> Choreo.nodes(system)[:db].kind
:postgres
iex> Choreo.nodes(system)[:db].name
"db"Diagram
@spec add_dataflow(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()
Adds a data-flow edge between two nodes.
This is semantically identical to connect/4 but renders with a
different default style (dashed, data-flow colour).
Examples
iex> system = Choreo.new() |> Choreo.add_service(:a) |> Choreo.add_service(:b) |> Choreo.add_dataflow(:a, :b)
iex> Choreo.edges(system)
[{:a, :b, 1}]
iex> get_in(system.edge_meta, [{:a, :b}, :type])
:dataflow
@spec add_load_balancer(t(), Yog.node_id(), keyword()) :: t()
Adds a load-balancer node to the system.
Options
:kind-:nginx,:haproxy,:alb,:cloudflare, etc.:name- display name:description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_load_balancer(:lb)
iex> Choreo.nodes(system)[:lb].type
:load_balancerDiagram
@spec add_network(t(), Yog.node_id(), keyword()) :: t()
Adds a network / infrastructure boundary node.
Options
:kind-:vpc,:subnet,:cdn,:dns,:firewall, etc.:name- display name:description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_network(:vpc)
iex> Choreo.nodes(system)[:vpc].type
:networkDiagram
@spec add_node(t(), Yog.node_id(), keyword()) :: t()
Adds a generic node to the system.
Use this when none of the typed builders fit.
Examples
iex> system = Choreo.new() |> Choreo.add_node(:misc, name: "Custom")
iex> Choreo.nodes(system)[:misc].type
:generic
iex> Choreo.nodes(system)[:misc].name
"Custom"
@spec add_queue(t(), Yog.node_id(), keyword()) :: t()
Adds a queue / messaging node to the system.
Options
:kind-:kafka,:rabbitmq,:sqs,:pubsub, etc.:name- display name:description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_queue(:q, kind: :kafka)
iex> Choreo.nodes(system)[:q].type
:queueDiagram
@spec add_service(t(), Yog.node_id(), keyword()) :: t()
Adds a service / application node to the system.
Options
:kind-:api,:worker,:web,:microservice, etc.:name- display name:description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_service(:api, name: "Gateway")
iex> Choreo.nodes(system)[:api].type
:service
iex> Choreo.nodes(system)[:api].name
"Gateway"Diagram
@spec add_storage(t(), Yog.node_id(), keyword()) :: t()
Adds a storage / blob node to the system.
Options
:kind-:s3,:nfs,:block,:glacier, etc.:name- display name:description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_storage(:s3)
iex> Choreo.nodes(system)[:s3].type
:storageDiagram
@spec add_user(t(), Yog.node_id(), keyword()) :: t()
Adds a user / external actor node to the system.
Options
:kind-:person,:device,:external_service, etc.:name- display name:description- tooltip / annotation text
Examples
iex> system = Choreo.new() |> Choreo.add_user(:client)
iex> Choreo.nodes(system)[:client].type
:userDiagram
Returns all cluster definitions in the system.
Examples
iex> system = Choreo.new() |> Choreo.add_cluster("vpc", label: "VPC")
iex> Choreo.clusters(system)
%{"cluster_vpc" => %{label: "VPC"}}
@spec connect(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()
Connects two nodes with a weighted edge.
Options
:cost- numeric weight used by MST and other algorithms (default:1):label- display label for the edge:protocol-:http,:https,:grpc,:tcp, etc.
Examples
iex> system = Choreo.new() |> Choreo.add_service(:a) |> Choreo.add_service(:b) |> Choreo.connect(:a, :b, cost: 5)
iex> Choreo.edges(system)
[{:a, :b, 5}]Diagram
@spec edges(t()) :: [{Yog.node_id(), Yog.node_id(), number()}]
Returns all edges in the system as a list of {from, to, cost} tuples.
Examples
iex> system = Choreo.new() |> Choreo.add_service(:a) |> Choreo.add_service(:b) |> Choreo.connect(:a, :b)
iex> Choreo.edges(system)
[{:a, :b, 1}]
Creates a new empty system diagram.
Options
:directed- whether the underlying graph is directed (default:true)
Examples
iex> system = Choreo.new()
iex> map_size(system.edge_meta)
0
iex> map_size(system.clusters)
0
iex> system = Choreo.new(directed: false)
iex> Yog.type(system.graph)
:undirected
@spec nodes(t()) :: %{required(Yog.node_id()) => map()}
Returns all nodes in the system as a map of id => data.
Examples
iex> system = Choreo.new() |> Choreo.add_service(:api)
iex> Map.keys(Choreo.nodes(system))
[:api]
Renders the system diagram to DOT format.
Options
:theme-:default,:dark, or:minimal- Any option accepted by
Yog.Render.DOT.to_dot/2
Examples
iex> system = Choreo.new() |> Choreo.add_service(:api)
iex> dot = Choreo.to_dot(system)
iex> String.contains?(dot, "digraph")
true
iex> String.contains?(dot, "api")
true
Returns the raw Yog.Graph struct underpinning the system.
Useful when you want to drop down to the raw Yog API.
Examples
iex> system = Choreo.new()
iex> Choreo.to_graph(system) == system.graph
true