Software dependency graph builder on top of Yog.
Choreo.Dependency models component relationships — modules, libraries,
applications, interfaces, and tests — to help visualize and analyze coupling,
layering, and circular dependencies.
When to use
Use Choreo.Dependency when refactoring a codebase, onboarding new
developers, or enforcing architectural boundaries. It surfaces hidden
cycles, measures instability, and identifies the deepest dependency chains
that slow down builds and tests.
Node types
:application— deployable service or app:library— external or shared library:module— internal code module:interface— API, contract, or protocol definition:test— test suite or spec
Edge types
:uses— general dependency:imports— explicit import / require:calls— runtime function call:inherits— inheritance / implementation:dev— development-only dependency
Further reading
Quick Start
deps =
Choreo.Dependency.new()
|> Choreo.Dependency.add_application(:api, label: "API Gateway")
|> Choreo.Dependency.add_library(:phoenix, label: "Phoenix")
|> Choreo.Dependency.add_module(:auth, label: "Auth Module")
|> Choreo.Dependency.depends_on(:api, :phoenix, type: :uses)
|> Choreo.Dependency.depends_on(:api, :auth, type: :calls)
dot = Choreo.Dependency.to_dot(deps)Diagram
Analysis
# Find circular dependencies
Choreo.Dependency.Analysis.cyclic_dependencies(deps)
#=> [[:repo, :service, :repo]]
# Impact analysis: what breaks if :auth changes?
Choreo.Dependency.Analysis.affected_by(deps, :auth)
#=> [:api, :web]
# Check layer violations
layers = %{api: 3, web: 2, repo: 1}
Choreo.Dependency.Analysis.layer_violations(deps, layers)
#=> [{:repo, :api, "repo (layer 1) calls api (layer 3)"}]
Summary
Functions
Adds an application node (deployable service or app).
Defines a cluster for grouping nodes visually (e.g., by team or layer).
Adds an interface node (API, contract, protocol).
Adds a library node (external or shared dependency).
Adds a module node (internal code unit).
Adds a test node (test suite or spec).
Creates a dependency edge from one component to another.
Returns all dependency edges as {from, to, weight} tuples.
Creates a new empty dependency graph.
Returns all node IDs in the dependency graph.
Returns all nodes of a given type.
Renders the dependency graph to DOT format.
Returns the raw Yog.Graph struct underpinning the dependency graph.
Types
@type t() :: %Choreo.Dependency{ clusters: %{required(String.t()) => map()}, edge_meta: %{optional({Yog.node_id(), Yog.node_id()}) => map()}, graph: Yog.graph() }
Functions
@spec add_application(t(), Yog.node_id(), keyword()) :: t()
Adds an application node (deployable service or app).
Options
:label— display label (defaults to the node id):description— tooltip text:cluster— cluster name for grouping
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_application(deps, :api, label: "API")
iex> Choreo.Dependency.nodes(deps)
[:api]
iex> Yog.node(deps.graph, :api).node_type
:application
iex> Yog.node(deps.graph, :api).label
"API"Diagram
Defines a cluster for grouping nodes visually (e.g., by team or layer).
Options
:parent— name of the parent cluster for nesting:label— display label (defaults to the cluster name):style—:filled,:rounded, etc.:fillcolor— background colour:color— border colour
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_cluster(deps, "core", label: "Core")
iex> deps.clusters["cluster_core"].label
"Core"
@spec add_interface(t(), Yog.node_id(), keyword()) :: t()
Adds an interface node (API, contract, protocol).
Options
:label— display label (defaults to the node id):description— tooltip text:cluster— cluster name for grouping
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_interface(deps, :contract)
iex> Choreo.Dependency.nodes(deps)
[:contract]
iex> Yog.node(deps.graph, :contract).node_type
:interfaceDiagram
@spec add_library(t(), Yog.node_id(), keyword()) :: t()
Adds a library node (external or shared dependency).
Options
:label— display label (defaults to the node id):description— tooltip text:cluster— cluster name for grouping
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_library(deps, :phx, label: "Phoenix")
iex> Choreo.Dependency.nodes(deps)
[:phx]
iex> Yog.node(deps.graph, :phx).node_type
:libraryDiagram
@spec add_module(t(), Yog.node_id(), keyword()) :: t()
Adds a module node (internal code unit).
Options
:label— display label (defaults to the node id):description— tooltip text:cluster— cluster name for grouping
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_module(deps, :auth)
iex> Choreo.Dependency.nodes(deps)
[:auth]
iex> Yog.node(deps.graph, :auth).node_type
:moduleDiagram
@spec add_test(t(), Yog.node_id(), keyword()) :: t()
Adds a test node (test suite or spec).
Options
:label— display label (defaults to the node id):description— tooltip text:cluster— cluster name for grouping
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_test(deps, :auth_test)
iex> Choreo.Dependency.nodes(deps)
[:auth_test]
iex> Yog.node(deps.graph, :auth_test).node_type
:testDiagram
@spec depends_on(t(), Yog.node_id(), Yog.node_id(), keyword()) :: t()
Creates a dependency edge from one component to another.
Direction reads as "from depends on to".
Options
:type—:uses,:imports,:calls,:inherits,:dev(default::uses):label— override label
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
...> |> Choreo.Dependency.depends_on(:api, :auth)
iex> Choreo.Dependency.edges(deps)
[{:api, :auth, 1}]
iex> deps.edge_meta[{:api, :auth}].type
:usesDiagram
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
...> |> Choreo.Dependency.depends_on(:api, :auth, type: :calls)
iex> deps.edge_meta[{:api, :auth}].type
:calls
iex> deps.edge_meta[{:api, :auth}].label
"calls"
@spec edges(t()) :: [{Yog.node_id(), Yog.node_id(), number()}]
Returns all dependency edges as {from, to, weight} tuples.
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
...> |> Choreo.Dependency.depends_on(:api, :auth)
iex> Choreo.Dependency.edges(deps)
[{:api, :auth, 1}]
@spec new() :: t()
Creates a new empty dependency graph.
Dependency graphs are always directed.
Examples
iex> deps = Choreo.Dependency.new()
iex> Choreo.Dependency.nodes(deps)
[]
iex> Choreo.Dependency.edges(deps)
[]
@spec nodes(t()) :: [Yog.node_id()]
Returns all node IDs in the dependency graph.
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api)
...> |> Choreo.Dependency.add_module(:auth)
iex> Enum.sort(Choreo.Dependency.nodes(deps))
[:api, :auth]
@spec nodes_of_type(t(), atom()) :: [Yog.node_id()]
Returns all nodes of a given type.
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:a)
...> |> Choreo.Dependency.add_application(:b)
...> |> Choreo.Dependency.add_library(:c)
iex> Enum.sort(Choreo.Dependency.nodes_of_type(deps, :application))
[:a, :b]
iex> Choreo.Dependency.nodes_of_type(deps, :library)
[:c]
iex> Choreo.Dependency.nodes_of_type(deps, :module)
[]
Renders the dependency graph to DOT format.
Options
:theme—:default,:dark, or aChoreo.Themestruct
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = deps
...> |> Choreo.Dependency.add_application(:api, label: "API")
...> |> Choreo.Dependency.add_module(:auth, label: "Auth")
...> |> Choreo.Dependency.depends_on(:api, :auth)
iex> dot = Choreo.Dependency.to_dot(deps)
iex> String.contains?(dot, "digraph")
true
iex> String.contains?(dot, "API")
true
iex> String.contains?(dot, "Auth")
true
Returns the raw Yog.Graph struct underpinning the dependency graph.
Examples
iex> deps = Choreo.Dependency.new()
iex> graph = Choreo.Dependency.to_graph(deps)
iex> graph.kind
:directed