Skip to content

Architecture Guarantee at Compile Time with Roslyn Analyzers

License

Notifications You must be signed in to change notification settings

AlbertoMonteiro/EnsureArch

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

2 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

EnsureArch ๐Ÿ—๏ธ

Architecture Guarantee at Compile Time with Roslyn Analyzers

EnsureArch is a library that uses Roslyn Analyzers to ensure your .NET application follows defined architecture rules, providing immediate feedback during compilation instead of relying on unit tests.

๐ŸŽฏ Motivation

Unlike libraries such as NetArchTest that validate architecture through unit tests, EnsureArch:

  • โœ… Immediate Feedback: Architecture errors are detected during compilation
  • โœ… IDE Integration: Warnings and errors appear directly in Visual Studio/VS Code
  • โœ… CI/CD Friendly: Build fails automatically if there are violations
  • โœ… Zero Overhead: Doesn't add code to the application runtime

๐Ÿš€ How It Works

flowchart TD
    Config[architecture.json] --> Analyzer[Roslyn Analyzer]
    Code[C# Code] --> Analyzer
    Analyzer --> Validation{Validate Dependencies}
    Validation -->|โœ… Valid| Success[Compilation OK]
    Validation -->|โŒ Invalid| Error[ARCH001: Architecture Violation]
    Error --> BuildFail[Build Fails]
Loading

๐Ÿ“ฆ Installation

dotnet add package EnsureArch.Analyzer

๐Ÿ”ง Configuration

1. Create architecture.json file

Add the file to your project root:

{
  "baseNamespace": "EnsureArch.Playground",
  "rules": [
    {
      "on": "Api",
      "allow": ["IoC", "ServiceDefaults", "Shareable"]
    },
    {
      "on": "Domain", 
      "allow": ["Shareable"]
    },
    {
      "on": "Data",
      "allow": ["Domain", "Shareable"]
    },
    {
      "on": "IoC",
      "allow": ["Api", "Domain", "Data", "ServiceDefaults", "Shareable"]
    },
    {
      "on": "ServiceDefaults",
      "allow": ["Shareable"]
    },
    {
      "on": "Shareable",
      "allow": []
    }
  ]
}

2. Include in .csproj

<Project Sdk="Microsoft.NET.Sdk">
  
  <ItemGroup>
    <PackageReference Include="EnsureArch.Analyzer" Version="1.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>
  </ItemGroup>
  
  <ItemGroup>
    <AdditionalFiles Include="architecture.json" />
  </ItemGroup>
  
</Project>

๐Ÿ—๏ธ Architecture Example

graph TB
    subgraph "Application Layers"
        Api[๐ŸŒ Api Layer<br/>Controllers, Endpoints]
        Domain[๐Ÿข Domain Layer<br/>Entities, Services]
        Data[๐Ÿ’พ Data Layer<br/>Repositories, DbContext]
        IoC[๐Ÿ”Œ IoC Layer<br/>Dependency Injection]
        ServiceDefaults[โš™๏ธ Service Defaults<br/>Common Configurations]
        Shareable[๐Ÿ“ฆ Shareable<br/>DTOs, Interfaces]
    end
    
    Api --> IoC
    Api --> ServiceDefaults
    Api --> Shareable
    
    Domain --> Shareable
    
    Data --> Domain
    Data --> Shareable
    
    IoC --> Api
    IoC --> Domain
    IoC --> Data
    IoC --> ServiceDefaults
    IoC --> Shareable
    
    ServiceDefaults --> Shareable
Loading

โŒ Detected Violations

Violation Example

// โŒ ARCH001: Namespace dependency violation
// The namespace 'EnsureArch.Playground.Domain' cannot depend on 'EnsureArch.Playground.Api' according to architecture.json

namespace EnsureArch.Playground.Domain;

using EnsureArch.Playground.Api; // โŒ Violation!

public class UserService
{
    // Domain cannot depend on Api
}

Error Message

ARCH001: The namespace 'EnsureArch.Playground.Domain' cannot depend on 'EnsureArch.Playground.Api' according to architecture.json

โœ… Correct Usage

// โœ… Allowed dependency
namespace EnsureArch.Playground.Domain;

using EnsureArch.Playground.Shareable; // โœ… Allowed!

public class UserService
{
    public UserDto GetUser(int id) // Using DTO from Shareable
    {
        // Implementation
    }
}

๐Ÿ” How the Analyzer Works

1. Configuration Loading

public static ArchitectureModel? LoadArchitecture(AnalyzerOptions options)
{
    var file = options.AdditionalFiles.FirstOrDefault(f => f.Path.EndsWith("architecture.json"));
    if (file == null) return null;

    using var fileStream = new FileStream(file.Path, FileMode.Open);
    var deserializer = System.Text.Json.JsonSerializer.Deserialize<ArchitectureModel>(fileStream);
    return deserializer;
}

2. Dependency Analysis

context.RegisterSyntaxNodeAction(ctx =>
{
    if (ctx.Node is UsingDirectiveSyntax { Name: { } @namespace } usingDirective)
    {
        var sourceNamespace = GetSourceNamespace(ctx.Node);
        var targetNamespace = @namespace.ToString();
        
        if (!IsAllowedDependency(sourceNamespace, targetNamespace))
        {
            var diagnostic = Diagnostic.Create(Rule, usingDirective.GetLocation(), 
                sourceNamespace, targetNamespace);
            ctx.ReportDiagnostic(diagnostic);
        }
    }
}, SyntaxKind.UsingDirective);

๐Ÿ“‹ Data Models

public record ArchitectureModel(string BaseNamespace, List<DependencyRule> Rules);

public record DependencyRule(string On, List<string> Allow);

๐Ÿ› ๏ธ Advanced Configuration

Multiple Projects

For solutions with multiple projects, each project can have its own architecture.json:

Solution/
โ”œโ”€โ”€ Project.Api/
โ”‚   โ”œโ”€โ”€ architecture.json
โ”‚   โ””โ”€โ”€ Project.Api.csproj
โ”œโ”€โ”€ Project.Domain/
โ”‚   โ”œโ”€โ”€ architecture.json
โ”‚   โ””โ”€โ”€ Project.Domain.csproj
โ””โ”€โ”€ Project.Data/
    โ”œโ”€โ”€ architecture.json
    โ””โ”€โ”€ Project.Data.csproj

Global Configuration

Or use a global file at the solution root:

<Project Sdk="Microsoft.NET.Sdk">
  <ItemGroup>
    <AdditionalFiles Include="../architecture.json" />
  </ItemGroup>
</Project>

๐Ÿ”ง CI/CD Integration

GitHub Actions

name: Architecture Validation

on: [push, pull_request]

jobs:
  validate-architecture:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '8.0.x'
    
    - name: Build and Validate Architecture
      run: dotnet build --configuration Release
      # Build will fail if there are ARCH001 violations

Azure DevOps

- task: DotNetCoreCLI@2
  displayName: 'Build and Validate Architecture'
  inputs:
    command: 'build'
    configuration: 'Release'
    # Pipeline will fail automatically with violations

๐ŸŽจ IDE Integration

Visual Studio

  • โœ… Errors appear in Error List
  • โœ… Red squiggles in code
  • โœ… Tooltips with error messages
  • โœ… Solution Explorer integration

VS Code

  • โœ… Problems appear in Problems Panel
  • โœ… C# Extension integration
  • โœ… IntelliSense warnings

๐Ÿ†š Comparison with NetArchTest

Aspect EnsureArch NetArchTest
Feedback โšก Compile time ๐Ÿงช Test time
IDE Integration โœ… Native โŒ Only in tests
CI/CD โœ… Build fails automatically โš ๏ธ Depends on test execution
Performance โœ… Zero runtime overhead โš ๏ธ Test overhead
Configuration ๐Ÿ“„ JSON file ๐Ÿ’ป C# code
Flexibility โš ๏ธ Limited to JSON โœ… Custom logic

๐Ÿ”ฎ Roadmap

  • Attribute Support: Attribute-based validation
  • Custom Rules: Plugin extensibility
  • Architecture Metrics: Complexity reports
  • Visual Studio Extension: GUI for configuration
  • VB.NET Support: Expand beyond C#
  • Integration Tests: External dependency validation

๐Ÿค Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

๐Ÿ“„ License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

๐Ÿ”— Useful Links


Keep your architecture clean and consistent with EnsureArch! ๐Ÿ—๏ธโœจ

About

Architecture Guarantee at Compile Time with Roslyn Analyzers

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages