Software Architecture/Engineering  

Automating Software Architecture Validation with ArchUnitNET

As enterprise applications grow, maintaining a clean architecture becomes increasingly challenging. New developers join the team, features evolve rapidly, and dependencies accumulate over time. Without consistent architectural governance, presentation layers may begin accessing databases directly, business logic may leak into controllers, and infrastructure concerns may spread throughout the codebase.

Traditional code reviews help identify these issues, but they rely on manual effort and can miss violations in large projects. ArchUnitNET provides an automated way to validate architectural rules as part of your test suite, helping teams detect structural problems before they reach production.

In this article, you'll learn how ArchUnitNET works, how to define architecture rules for ASP.NET Core applications, and how to integrate architecture validation into your CI/CD pipeline.

Why Architecture Validation Matters

A well-designed architecture improves:

  • Maintainability

  • Testability

  • Scalability

  • Team productivity

  • Code consistency

Over time, however, architectural boundaries can erode without automated validation.

Common examples include:

  • Controllers accessing databases directly

  • Domain models referencing infrastructure packages

  • Circular project dependencies

  • Business logic inside UI components

  • Unauthorized dependency usage

Automated architecture tests help identify these issues early.

What Is ArchUnitNET?

ArchUnitNET is a .NET library that allows developers to express architectural rules as executable tests.

Instead of documenting architectural guidelines and hoping developers follow them, you can verify them automatically during every build.

For example:

Developer
     │
Source Code
     │
Architecture Tests
     │
Pass or Fail

If an architectural rule is violated, the test fails, preventing the issue from progressing unnoticed.

Typical Clean Architecture

A common layered architecture might look like this:

Presentation
      │
Application
      │
Domain
      │
Infrastructure

Each layer should only depend on approved layers.

For example:

  • Presentation → Application

  • Application → Domain

  • Infrastructure → Domain

The Domain layer should generally avoid dependencies on Presentation or Infrastructure.

Installing ArchUnitNET

Add the NuGet package to your test project.

dotnet add package ArchUnitNET

Architecture tests should typically live in a dedicated test project alongside unit and integration tests.

Loading the Architecture

The first step is loading the assemblies to inspect.

using ArchUnitNET.Loader;

var architecture = new ArchLoader()
    .LoadAssemblies(typeof(Program).Assembly)
    .Build();

The loader analyzes the compiled assemblies rather than source code.

Defining Layers

Create reusable layer definitions.

using static ArchUnitNET.Fluent.ArchRuleDefinition;

var presentationLayer =
    Types().That().ResideInNamespace("MyApp.Api");

var applicationLayer =
    Types().That().ResideInNamespace("MyApp.Application");

var domainLayer =
    Types().That().ResideInNamespace("MyApp.Domain");

Consistent namespace organization makes architecture validation easier to maintain.

Validating Layer Dependencies

Suppose your architecture requires the Domain layer to remain independent.

A rule may look like this:

var rule =
    domainLayer
        .Should()
        .NotDependOnAny(applicationLayer);

If a domain class references the application layer, the architecture test fails.

Preventing Controller-to-Repository Dependencies

Controllers should usually communicate through application services rather than repositories.

Example rule:

var controllers =
    Types().That().ResideInNamespace("MyApp.Api.Controllers");

var repositories =
    Types().That().ResideInNamespace("MyApp.Infrastructure.Repositories");

An architecture rule can verify that controllers do not directly depend on repository implementations.

Enforcing Dependency Injection

A common architectural guideline is depending on abstractions rather than concrete implementations.

Architecture tests can help identify areas where concrete infrastructure classes are referenced directly instead of interfaces.

This reinforces dependency inversion across the application.

Validating Naming Conventions

Architecture validation is not limited to dependencies.

Examples include:

  • Controllers ending with Controller

  • Services ending with Service

  • Repositories ending with Repository

  • Interfaces beginning with I

Consistent naming improves readability across large codebases.

Preventing Circular Dependencies

Circular dependencies increase coupling and complicate maintenance.

Example:

Project A
     │
Project B
     │
Project C
     │
Project A

Architecture tests help detect these cycles before they become widespread.

Running Architecture Tests in CI/CD

A typical pipeline includes architecture validation alongside other automated tests.

Source Code
      │
Build
      │
Unit Tests
      │
Architecture Tests
      │
Integration Tests
      │
Deployment

Failing architecture tests stop structural issues before deployment.

Monitoring Architectural Health

Useful indicators include:

  • Architecture test pass rate

  • Dependency violations

  • Layer boundary violations

  • Circular dependency count

  • Unauthorized package references

Tracking these indicators helps teams identify long-term architectural trends.

Comparison of Validation Approaches

ApproachAdvantagesLimitations
Manual Code ReviewHuman judgmentTime-consuming and inconsistent
Documentation OnlyEasy to createCannot enforce compliance
Static AnalysisDetects code issuesLimited architectural awareness
ArchUnitNETAutomated architecture validationRequires well-defined architectural rules

Many teams combine architecture tests with static analysis and code reviews.

Common Mistakes

MistakeBetter Approach
Defining overly complex rulesStart with simple, meaningful architectural constraints
Using inconsistent namespacesMaintain predictable project organization
Ignoring architecture test failuresTreat violations as technical debt requiring investigation
Writing rules that duplicate compiler checksFocus on architectural boundaries rather than language rules
Running architecture tests only occasionallyExecute them as part of every CI build

Troubleshooting

Tests Report Unexpected Violations

Verify:

  • Namespace organization

  • Assembly loading

  • Layer definitions

  • Rule configuration

Incorrect namespaces often lead to inaccurate architecture results.

Architecture Tests Become Difficult to Maintain

Review whether:

  • Rules reflect the current architecture.

  • Layer definitions remain consistent.

  • Project organization has changed.

Architecture tests should evolve alongside the application.

Build Time Increases

Architecture validation generally executes as part of the test suite.

If execution time becomes significant:

  • Load only the required assemblies.

  • Remove redundant rules.

  • Group related architecture tests logically.

Best Practices

  • Keep architectural rules simple and focused.

  • Organize projects with consistent namespaces.

  • Validate architectural boundaries in every build.

  • Version architecture rules alongside application changes.

  • Review failed architecture tests before suppressing them.

  • Combine architecture validation with code reviews and static analysis.

  • Regularly update rules as the architecture evolves.

Conclusion

Maintaining a clean software architecture requires more than documentation and code reviews. As applications evolve, architectural boundaries can gradually erode unless they are continuously verified. ArchUnitNET enables teams to express architectural principles as executable tests, making architecture validation an automated part of the development process.

By integrating architecture tests into CI/CD pipelines, enforcing dependency rules, validating naming conventions, and monitoring architectural health over time, development teams can reduce architectural drift and maintain more consistent, scalable, and maintainable ASP.NET Core applications.

Frequently Asked Questions

What types of rules can ArchUnitNET validate?

ArchUnitNET can validate architectural concerns such as layer dependencies, namespace organization, naming conventions, project boundaries, and dependency relationships between assemblies and types.

Should architecture tests replace code reviews?

No. Architecture tests automate repeatable structural checks, while code reviews remain important for evaluating business logic, design decisions, readability, and maintainability.

Is ArchUnitNET only useful for Clean Architecture?

No. It can be applied to many architectural styles, provided the project has well-defined structural rules that can be expressed as automated tests.

How often should architecture tests run?

For most projects, architecture tests should execute alongside the regular automated test suite in the CI/CD pipeline so that architectural violations are detected as early as possible.