ExArchUnit

Hex.pmDocsCoverageMade with AIVerified by Humans

Enforce architecture rules in Elixir projects — without touching production code. Define layer boundaries in a standalone arch.exs file, run mix arch.check, and get CI-friendly output. Rules live outside your application, so your production modules stay clean.

Quick Start

1. Add the dependency:

defp deps do
  [
    {:ex_arch_unit, "~> 0.1.0", only: [:dev, :test], runtime: false}
  ]
end

2. Create arch.exs next to mix.exs:

layers do
  layer :web, "MyAppWeb.*"
  layer :domain, "MyApp.Domain.*"

  allow :web, depends_on: [:domain]
  forbid :domain, depends_on: [:web]
end

3. Run:

mix arch.check

That's it. No test file needed. It enforces your arch.exs rules and exits with code 1 on violations. Add it to CI and you're done.

Two Ways to Enforce Rules

Option A: mix arch.check (recommended for most users)

Write your rules in arch.exs and run mix arch.check. This is the simplest path — one config file, one command. Covers allow and forbid layer rules.

mix arch.check                            # uses arch.exs
mix arch.check --config path/to/arch.exs  # custom config path
mix arch.check --no-cache                 # bypass graph cache

Option B: ExUnit tests (when you need more)

Write a test file with use ExArchUnit when you need capabilities beyond what arch.exs offers:

defmodule ArchitectureTest do
  use ExUnit.Case, async: true
  use ExArchUnit, config: "arch.exs"

  # Cycle detection (not available in arch.exs)
  test "domain has no cycles" do
    assert_no_cycles prefix: "MyApp.Domain.*"
  end

  # Ad-hoc rule outside the config
  test "controllers don't call repo directly" do
    forbid "MyAppWeb.Controllers.*", depends_on: "MyApp.Repo.*"
  end
end
mix test

Note: use ExArchUnit also auto-enforces your arch.exs layer rules during setup_all by default, so you don't need to duplicate them as tests.

Features

Why Not Boundary?

Boundary enforces module boundaries at compile time using use Boundary attributes inside your production modules. This means architecture rules are scattered across your codebase and coupled to the modules they constrain.

ExArchUnit takes the opposite approach: rules live entirely outside your production code in a standalone arch.exs file. Your application modules don't know they're being checked. This means:

If you prefer compile-time enforcement baked into your modules, use Boundary. If you want rules separate from production code, use ExArchUnit.

ExUnit API

These macros are available inside test modules that use ExArchUnit:

use ExArchUnit also auto-enforces arch.exs layer rules during setup_all. Disable if needed:

use ExArchUnit, config: "arch.exs", enforce_config_rules: false

arch.exs DSL

Supported DSL entries:

Umbrella Support

In umbrella projects, ExArchUnit analyzes umbrella child apps by default.

App discovery strategy:

  1. Mix.Project.apps_paths/0 when available
  2. Fallback filesystem scan of apps/*/mix.exs when needed

Set include_deps true if you explicitly want to include dependencies under _build.

Reference

Selector Semantics

Selectors are string-based and deterministic:

Regex selectors are not part of v0.1.

Dependency Semantics

Default dependency source:

Optional edge source:

Not treated as dependencies in v0.1:

Caching and Invalidation

ExArchUnit stores the graph in :persistent_term for fast read access.

The cache invalidates when:

Environment Variables

ExArchUnit_NO_CACHE=1 mix test         # force rebuild every time
ExArchUnit_PROFILE=1  mix test         # print graph build stats
ExArchUnit_PROFILE=1  mix arch.check   # also works with arch.check

Performance

Development

This project uses just as a command runner. Run just to see all available recipes.

just init              # Install Hex dependencies from mix.lock
just build             # Compile source and generate ExDoc HTML
just clean             # Remove _build and fetched dependencies

just ci                # Format, test, and build
just test              # ExUnit suite with coverage and graph build profiling
just test-nocache      # Same as test but with graph cache disabled (forces xref rebuild)

just code-format       # Auto-format all source files
just code-benchmark    # Benchmark graph build on a synthetic umbrella

Tune benchmark size:

ExArchUnit_BENCH_APPS=6 ExArchUnit_BENCH_MODULES_PER_APP=120 just code-benchmark

CI

GitHub Actions runs formatting, tests, and docs on every push. See ci.yml.

License

MIT — see LICENSE.