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.
Returns all edges with their metadata as {from, to, weight, meta} 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.
Collapses parallel edges into a simple Graph for algorithm analysis.
Types
@type t() :: %Choreo.Dependency{ clusters: %{required(String.t()) => map()}, edge_meta: %{optional(Yog.Multi.Graph.edge_id()) => map()}, graph: Yog.Multi.Graph.t() }
Functions
Adds an application node (deployable service or app).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_application(deps, :api, label: "API")
iex> Choreo.Dependency.nodes(deps)
[:api]
iex> Map.get(deps.graph.nodes, :api).node_type
:application
iex> Map.get(deps.graph.nodes, :api).label
"API"Diagram
Defines a cluster for grouping nodes visually (e.g., by team or layer).
Options
:parent(String.t/0):label(String.t/0):style(String.t/0):fillcolor(String.t/0):color(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_cluster(deps, "core", label: "Core")
iex> deps.clusters["cluster_core"].label
"Core"
Adds an interface node (API, contract, protocol).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_interface(deps, :contract)
iex> Choreo.Dependency.nodes(deps)
[:contract]
iex> Map.get(deps.graph.nodes, :contract).node_type
:interfaceDiagram
Adds a library node (external or shared dependency).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_library(deps, :phx, label: "Phoenix")
iex> Choreo.Dependency.nodes(deps)
[:phx]
iex> Map.get(deps.graph.nodes, :phx).node_type
:libraryDiagram
Adds a module node (internal code unit).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth:image(String.t/0)
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_module(deps, :auth)
iex> Choreo.Dependency.nodes(deps)
[:auth]
iex> Map.get(deps.graph.nodes, :auth).node_type
:moduleDiagram
Adds a test node (test suite or spec).
Options
:label(String.t/0):description(String.t/0):cluster(String.t/0):shape(atom/0):fillcolor(String.t/0):fontcolor(String.t/0):style(String.t/0):penwidth
Examples
iex> deps = Choreo.Dependency.new()
iex> deps = Choreo.Dependency.add_test(deps, :auth_test)
iex> Choreo.Dependency.nodes(deps)
[:auth_test]
iex> Map.get(deps.graph.nodes, :auth_test).node_type
:testDiagram
Creates a dependency edge from one component to another.
Direction reads as "from depends on to".
Limitation
At most one edge is allowed per
(from, to)pair. Adding a second dependency between the same components raisesArgumentError. Multigraph support (parallel edges) is planned for a future release.
Options
:type:label(String.t/0)
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> [{_, _, _, meta}] = Choreo.Dependency.edges_with_meta(deps)
iex> meta.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> [{_, _, _, meta}] = Choreo.Dependency.edges_with_meta(deps)
iex> meta.type
:calls
iex> meta.label
"calls"
@spec edges(t()) :: [{Yog.node_id(), Yog.node_id(), any()}]
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 edges_with_meta(t()) :: [{Yog.node_id(), Yog.node_id(), any(), map()}]
Returns all edges with their metadata as {from, to, weight, meta} tuples.
@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
@spec to_simple_graph( t(), keyword() ) :: Yog.Graph.t()
Collapses parallel edges into a simple Graph for algorithm analysis.