Qt Software Insights | Software Development Resources

Architecture as Code: A Developer-Friendly Approach to Architecture Verification

Written by Prof. Dr. Rainer Koschke | Apr 21, 2026 11:49:01 AM

 

 

Software architecture has an uncomfortable secret: in most projects, it lives primarily in people's heads. There might be a diagram in a dusty wiki, a few paragraphs in an onboarding doc, or a whiteboard photo from a sprint planning session three years ago. But a living, verifiable, machine-readable description of the architecture? Rarely.

This gap between intended architecture and actual implementation is more than a documentation problem — it is a quality problem. The good news is that there are several practical ways to tackle it, depending on where your project stands today. This article focuses on one particularly developer-friendly approach: Architecture as Code.

Why Architecture Drift Happens

When architecture exists only as informal documentation, there is no automated way to check whether the code actually follows the rules. Developers make reasonable local decisions that, in aggregate, slowly erode the intended structure. New team members misinterpret the design. Dependencies creep in where they shouldn't. Over time, the gap widens — and often goes unnoticed until it causes a real problem.

The reflexion analysis method, introduced by Murphy, Notkin, and Sullivan in their foundational 1995 paper1), addresses this directly. The idea is: define an architecture model, map your source code entities to architectural components, and then algorithmically compare the two graphs to surface convergences (expected dependencies that exist), divergences (unexpected dependencies), and absences (expected dependencies that are missing). This gives teams a concrete, reproducible way to audit conformance continuously2).

The challenge has always been in the how — specifically, how you describe the architecture model and specify the mapping between code and components.

There Is More Than One Way In

Architecture as Code and Architecture Recovery are not limited to only one particular scenario — in practice, both can be used, whether you are documenting an architecture for the first time or enforcing one that already exists. The meaningful difference lies in how you work. Architecture as Code is programmatic: you write rules, run them, fix issues, and run them again. The more regular and consistent the relationship between your code structure and your architecture, the more you can automate. Architecture Recovery, on the other hand, is interactive and manual — which can actually be an advantage when code and architecture diverge significantly and you need to quickly experiment, shift things around, and get a feel for the structure before committing to rules.

The two approaches can also be combined. A well-structured part of a codebase might be described cleanly in Architecture as Code, while a messier legacy subsystem is mapped interactively first. Over time, as the architecture stabilises through iterative recovery work, those manual mappings can be codified into Architecture as Code rules — making the model reproducible and easier to maintain going forward.

How Existing Tools Handle Architecture Description

For teams that do have an architecture to express, the question becomes how to describe it. Most tools that support reflexion analysis have taken one of two approaches.

The original method, as proposed by Murphy et al., used regular expressions to map implementation artifacts to architectural components. While expressive enough for small systems, regular expressions quickly become unwieldy as complexity grows.

Most modern tools have moved to graphical interfaces: drag components onto each other to define mappings, rearrange boxes to model your hierarchy. For small or mid-sized systems this works well, but at scale these tools hit real limitations:

    • Visual clutter: large systems with many dependencies become genuinely hard to navigate in a diagram editor.
    • Painful refinements: decomposing a large component into sub-components requires significant manual rearranging. Moving elements around to make room is tedious and error-prone.
    • Poor version control: graphical model files don't diff well. Understanding what changed between two versions of your architecture model is much harder than it should be.
    • Limited automation: mapping is largely manual. Researchers have explored automated approaches, but none work reliably enough for production use.

Architecture as Code: A Procedural, Developer-Friendly Alternative

Architecture as Code, used by Axivion for its advanced architecture verification, takes a different path. Instead of a graphical tool or a stand-alone DSL, architecture and mapping are described as Python code — specifically, as an internal domain-specific language (DSL) embedded in Python.

The architecture itself is defined declaratively using a set of provided Python classes and methods:

ARCH = Architecture(
    "My Architecture",
    Component("App"),
    Component("EngineCtrl"),
    Component("Sensor"),
    ComponentWithInterface("HW")
)

app = ARCH.App
enginectrl = ARCH.EngineCtrl

app.depends_on(enginectrl)
app.depends_on(sensor)

The mapping from source code to architectural components is expressed with equal clarity:

# Everything in the component is private...

MAPPING.add_mapping("src/hw", hw, is_private=True)

# ...except the public header

MAPPING.add_mapping("src/hw/hw_public.h", hw, is_private=False)

Mapping rules follow a last-match-wins approach, making it easy to define general rules and then carve out exceptions — a pattern that will feel natural to any developer who has worked with namespace visibility or build system filters.

The libraries for the Python-based DSL are included in Axivion and can be utilized as needed.

What This Means for Development Teams

Beyond the elegance of the syntax, this approach delivers concrete day-to-day benefits.

It works with your existing toolchain.
Because the architecture description is just Python, your IDE, debugger, linter, and formatter all work with it out of the box. There is no new tool to learn for editing, and no special viewer required to understand what you are looking at.

Version control is first-class. 
Architecture descriptions live in Git alongside the code they describe. Diffs are readable. Code reviews of architecture changes work the same way as reviews of any other change. History is fully traceable.

It is extensible. 
Teams can subclass the provided DSL components to model domain-specific concepts. The ComponentWithInterface class — which separates a component's public interface from its private internals — illustrates how naturally this pattern extends. Any Python library can be pulled in; any Python logic can drive the model.

Architecture can be computed, not just declared. 
Because the description is executable Python, it is feasible to bootstrap an initial architecture model by traversing the existing dependency graph and generating Python code from it. Teams can start from an auto-generated skeleton and refine it manually, rather than facing a blank slate. This makes Architecture as Code a viable bridge even when coming out of an architecture recovery effort.

Violations are actionable. 
Once the reflection analysis runs, results can be output in any format useful to your team — a list of violations in the console, a report in a CI pipeline, etc. Architecture conformance becomes a gate, not a hope.

Choosing Your Starting Point

The right approach depends on your situation:

    • You have an architecture and want to enforce it: Architecture as Code is a strong fit. Describe it in Python, map it to your code, and run the analysis in CI.
    • You have fragments of documentation but nothing complete: Axivion's Architecture Recovery lets you build on those fragments interactively, iterating until the model converges with the implementation.
    • You have no documentation at all: Architecture Archaeology provides a path from zero: sketch a hypothesis with the Axivion architecture modelling tool, map it to the code, and refine from there.

In all cases, the end goal is the same: a validated, living architecture model that can be checked continuously against the code.

Getting Started

Architecture as Code in the Axivion supports C, C++, C#, CUDA C++, and Rust today. For other languages, dependency graphs can be generated via the Language Server Protocol (LSP), making the approach broadly applicable.

If your team already uses static analysis in CI, adding architecture conformance checking is a natural extension. If you are not sure where your architecture stands, starting with a recovery effort is often the fastest way to establish a baseline worth verifying. To ensure that architecture is then verified continuously, you can use Axivion Architecture Verification.

Either way, turning architecture from an invisible assumption into a visible, measurable artifact is a meaningful shift — and one that pays dividends every time your codebase changes.

 

1) Gail C. Murphy, David Notkin, and Kevin Sullivan. Software reflexion models: Bridging the gap between source and high-level models. In ACM SIGSOFT Symposium on the Foundations of Software Engineering, pages 18–28, 1995.  

2) See R. Koschke and D. Simon, "Hierarchical reflexion models," In: Proceedings of the 10th Working Conference on Reverse Engineering, 2003. pp. 36-45, doi: 10.1109/WCRE.2003.1287235.