oullette
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:
-
Enforcing domain boundaries
-
Maintaining layered architectures
-
Preventing unwanted dependencies / insecure patterns
-
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
-
Has anyone built something like this already and I’m just not finding it in my searches?
-
Which approach seems most promising?
-
What architectural patterns would you want to enforce?
-
Would this be valuable as a standalone package?
-
Are there specific challenges in Elixir’s compilation model that would make this difficult?
-
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?
Trending in Discussions
Other Trending Topics
Chat & Discussions>Discussions
Latest on Elixir Forum
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #phoenix_html
- #iex
- #graphql
- #ai
- #genstage
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #security
- #hex










First 6 of 6 Posts
garrison
This is not really in my areas of expertise or interest, but I will note two things about the DSL you proposed:
MyApp.Domain) instead of stringsoullette
Sure! The basic example I showed could be implemented with just functions and atoms instead of strings. I simplified it for illustration purposes.
Where metaprogramming would become valuable is in:
Creating a truly fluent API that maintains context between chained calls
Supporting pattern matching on module hierarchies (like MyApp.{domain}.**)
Analyzing dependencies at compile time vs runtime
Generating custom test failures with detailed violation reporting
Extracting and evaluating architectural rules from module attributes or configs
The goal would be to make the rules both expressive and powerful enough to handle complex architectural patterns while remaining readable to developers who aren’t familiar with the tool’s internals.
garrison
Maybe I am overlooking something but I don’t think any of these points would require macros, except maybe the module attribute part, but I’m not entirely sure what that would be useful for.
I suppose the pattern thing is a point in favor of strings, though.
axelson
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, andMyApp.Applicationcould be different boundaries and you could enforce thatMyApp.Domaindoesn’t call intoMyApp.Web, although it’s also easy to add a few exceptions, like allowing calls toMyApp.Web.Endpointto allow creating permalinks fromMyApp.Web.Endpoint.static_url.And Boundary can also generate a graphviz graph of your boundaries which is helpful for visualizing and understanding them.
josh.nz
In addition to the Boundary Github page linked by axelson, Saša Jurić gives a talk about it here: https://www.youtube.com/watch?v=CKeYV_YOLyE which you might find helpful. You may wish to skip over the umbrella app section at the start of the talk.
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:
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.