oullette

oullette

Exploring ArchUnit-like Architecture Testing for Elixir

Like most engineers these days, I’ve been thinking about the intersection of LLM driven development and using Elixir to do so. After being particular inspired by this blog post, I’ve been looking at implementing architecture testing in Elixir projects and wondering if there’s an equivalent to Java’s ArchUnit. After some research and brainstorming, I’d like to share some thoughts and potential approaches to spark a community discussion.

The Problem

In larger Elixir codebases, maintaining architectural boundaries becomes increasingly important - especially as we navigate the limitations of pumping LLM agents full of context. While Elixir’s design encourages good practices, we still need ways to codify:

  1. Enforcing domain boundaries

  2. Maintaining layered architectures

  3. Preventing unwanted dependencies / insecure patterns

  4. Ensuring architectural decisions are continually followed by the team (or even LLM coding agents)

Java developers have ArchUnit, which provides a fluent API for defining and testing architectural rules:

noClasses().that().resideInAPackage("..source..")
    .should().dependOnClassesThat().resideInAPackage("..foo..")

Now tell me that doesn’t look a lot like a functional pipeline chain…

Potential Approaches in Elixi

I see two main paths forward:

1. Custom Credo Rules

We could extend Credo with custom rules for architecture testing:

# In .credo.exs
%{
  configs: [
    %{
      name: "default",
      checks: [
        {MyProject.ArchitectureCheck.DomainBoundaries, []},
      ]
    }
  ]
}

With implementation like:

defmodule MyProject.ArchitectureCheck.DomainBoundaries do
  use Credo.Check, category: :design, base_priority: :high

  def run(%SourceFile{} = source_file, params) do
    # Parse AST and check for violations
    # Return list of issues found
  end
end

2. Custom DSL with Metaprogramming

Alternatively (and where I’m personally leaning), we could leverage Elixir’s metaprogramming to create an expressive DSL similar to ArchUnit and further leans into pipelines:

defmodule MyApp.ArchitectureTest do
  use ExUnit.Case
  use ArchElixir.DSL
  
  test "domain modules should not depend on web modules" do
    architecture_rule do
      modules_that()
      |> reside_in_namespace("MyApp.Domain")
      |> should()
      |> not_depend_on_modules_that()
      |> reside_in_namespace("MyApp.Web")
    end
  end
  
  test "layered architecture is respected" do
    architecture_rule do
      layers()
      |> define("Web", "MyApp.Web")
      |> define("Application", "MyApp.Application")
      |> define("Domain", "MyApp.Domain")
      |> where_layer("Web")
      |> may_only_access_layers(["Application"])
      |> where_layer("Application")
      |> may_only_access_layers(["Domain"])
    end
  end
end

This would require building:

  • A fluent rule builder API

  • A dependency analyzer (walking the AST similar to Credo and Sobelow)

  • A rule evaluator

  • ExUnit integration

Comparing Approaches

Custom DSL

Strengths

  • Highly expressive and readable rules

  • Flexible and extensible for complex patterns

  • Seamless ExUnit integration

  • Runtime analysis catches dynamic dependencies

  • Customized reporting

  • Paves the path for Dynamic Application Security Testing (which is worth exploring for me as a security professional)

Weaknesses

  • Significant development effort

  • Potential performance overhead

  • Limited community support

  • Complex dependency analysis

Credo Rules

Strengths

  • Integration with existing tooling

  • Static analysis benefits (speed, early feedback)

  • Community and ecosystem support

  • Lower implementation barrier

  • IDE integration

Weaknesses

  • Less expressive API

  • Limited analysis depth

  • Constrained by Credo’s design

  • Less natural for complex rules

Questions for the Community

  1. Has anyone built something like this already and I’m just not finding it in my searches?

  2. Which approach seems most promising?

  3. What architectural patterns would you want to enforce?

  4. Would this be valuable as a standalone package?

  5. Are there specific challenges in Elixir’s compilation model that would make this difficult?

  6. How could we handle dependencies across umbrella apps?

Again, I’m particularly interested in Elixir’s metaprogramming capabilities and how they could enable an elegant DSL for architecture testing. The ability to define rules that match the mental model of our architectural constraints seems valuable.

What are your thoughts? Would you even use such a tool?

Most Liked

axelson

axelson

Scenic Core Team

Not an exact fit but Boundary handles the majority of what your examples are covering (although you probably have more advanced rules in mind as well)

https://github.com/sasa1977/boundary

It supports defining what the boundaries are in a project, e.g. MyApp.Domain, MyApp.Web, and MyApp.Application could be different boundaries and you could enforce that MyApp.Domain doesn’t call into MyApp.Web, although it’s also easy to add a few exceptions, like allowing calls to MyApp.Web.Endpoint to allow creating permalinks from MyApp.Web.Endpoint.static_url.

And Boundary can also generate a graphviz graph of your boundaries which is helpful for visualizing and understanding them.

yoavgeva

yoavgeva

Boundary is great for compile-time API guards, but if you want the full ArchUnit experience — transitive dependency checks, glob-based module selection, cycle detection, naming conventions, coupling metrics — I built arch_test exactly for this.

Rules live in plain ExUnit tests, work from compiled bytecode, and require zero changes to production code:

defmodule MyApp.ArchTest do
  use ExUnit.Case
  use ArchTest

  test "services don't call repos directly" do
    modules_matching("MyApp.**.*Service")
    |> should_not_depend_on(modules_matching("MyApp.**.*Repo"))
  end

  test "no circular dependencies" do
    modules_matching("MyApp.**") |> should_be_free_of_cycles()
  end
end

It also supports layered/onion/modulith architecture patterns, Martin coupling metrics, and a violation freeze for gradual adoption on existing codebases.

Boundary and arch_test actually compose well — Boundary for hard build-time API boundaries, arch_test for everything else.

garrison

garrison

This is not really in my areas of expertise or interest, but I will note two things about the DSL you proposed:

  1. There is no metaprogramming needed as far as I can tell; you can just use functions
  2. You can just use the module names as atoms (MyApp.Domain) instead of strings

Where Next?

Popular in Discussions Top

CharlesO
Erlang :list.nth simple, but 1 - based nth(1, [H|_]) -> H; nth(N, [_|T]) when N > 1 -> nth(N - 1, T). Elixir Enum.at … coo...
New
ricklove
I was just introduced to Elixir and Phoenix. I was told about the 2 million websocket test that was done 2 years ago. From my research, t...
New
restack_oslo
Hello, Please pardon me for any faux paux. I am 46 and this is my first time on a forum of any kind. I wanted to to get answers from tho...
New
rower687
Hi all, I’ve been reading a lot about the “let it crash” term and how supervising processes and the whole messaging passing make an elixi...
New
wmnnd
The Go vs Elixir thread got me thinking: Would it be too hard to implement a simple mechanism for creating Go-style static app binaries f...
New
Fl4m3Ph03n1x
Background This question comes mainly from my ignorance. Today is Black Friday, one of my favorite days of the year to buy books. One boo...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

Other popular topics Top

KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36654 110
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 54006 488
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New

We're in Beta

About us Mission Statement