Wird geladen...

Schichtenarchitektur und Clean Architecture in C#

Lernen Sie Schichtenarchitektur und Clean Architecture in C#, um wartbare und testbare Anwendungen zu entwickeln.

Wenn Softwareprojekte wachsen, wird es immer wichtiger, den Code organisiert und wartbar zu halten. Zu diesem Zweck werden die Schichtenarchitektur (Layered Architecture) und ihre moderne Weiterentwicklung, die Clean Architecture, verwendet. Beide Modelle bieten eine starke Grundlage, um Abhängigkeiten zu verwalten, Verantwortlichkeiten zu trennen und testbare, erweiterbare Systeme zu entwickeln.


Was ist eine Schichtenarchitektur?

Die Schichtenarchitektur ist ein klassisches Architekturmodell, das eine Anwendung in verschiedene Schichten mit unterschiedlichen Verantwortlichkeiten unterteilt. Die gängigste Form besteht aus 3 oder 4 Schichten:

Die Abhängigkeitsrichtung verläuft von oben nach unten: Die UI-Schicht kann auf Business zugreifen, Business auf Data Access, aber nicht umgekehrt.


Klassischer Ablauf der Schichtenarchitektur

Das folgende Diagramm zeigt ein einfaches Beispiel:


graph TD;
  A[UI-Schicht] --> B[Business-Schicht];
  B --> C[Data-Access-Schicht];
  C --> D[Datenbank];

Diese Struktur reicht für einfache CRUD-Systeme aus, kann aber mit der Zeit komplex werden, wenn die Schichten eng gekoppelt sind.


Was ist Clean Architecture?

Die Clean Architecture wurde von Robert C. Martin (Uncle Bob) entwickelt und richtet die Abhängigkeiten auf den Kern (Domain) aus. Ziel ist es, die Geschäftslogik vollständig von der Außenwelt zu isolieren.

Die Schichten sind nun von außen nach innen abhängig:


graph LR;
  A[UI / API] --> B[Application];
  B --> C[Domain];
  C --> D[Infrastructure (Extern)];

Der wichtigste Unterschied: Die Domain-Schicht hängt nie von äußeren Schichten ab.


Schichten und Verantwortlichkeiten in der Clean Architecture


Abhängigkeitsregel (Dependency Rule)

Das wichtigste Prinzip der Clean Architecture lautet: Abhängigkeiten zeigen immer nach innen. Äußere Schichten kennen die inneren, aber die inneren wissen nichts über die äußeren.


// Domain-Schicht (keine Abhängigkeiten)
public class Order
{
    public int Id { get; set; }
    public decimal Total { get; set; }
    public bool IsApproved { get; private set; }

    public void Approve()
    {
        if (Total <= 0)
            throw new InvalidOperationException("Der Betrag muss größer als 0 sein.");
        IsApproved = true;
    }
}

// Application-Schicht
public interface IOrderRepository
{
    Task<Order> GetByIdAsync(int id);
    Task SaveAsync(Order order);
}

public class ApproveOrderUseCase
{
    private readonly IOrderRepository _repository;
    public ApproveOrderUseCase(IOrderRepository repository)
        => _repository = repository;

    public async Task ExecuteAsync(int id)
    {
        var order = await _repository.GetByIdAsync(id);
        order.Approve();
        await _repository.SaveAsync(order);
    }
}

// Infrastructure-Schicht
public class EfOrderRepository : IOrderRepository
{
    private readonly AppDbContext _context;
    public EfOrderRepository(AppDbContext context) => _context = context;

    public Task<Order> GetByIdAsync(int id) =>
        _context.Orders.FirstOrDefaultAsync(o => o.Id == id);

    public Task SaveAsync(Order order)
    {
        _context.Update(order);
        return _context.SaveChangesAsync();
    }
}

// Presentation-Schicht (API)
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly ApproveOrderUseCase _useCase;
    public OrdersController(ApproveOrderUseCase useCase) => _useCase = useCase;

    [HttpPost("{id}/approve")]
    public async Task Approve(int id)
    {
        await _useCase.ExecuteAsync(id);
        return Ok();
    }
}

Wie man sieht, kennt die Domain-Schicht keine Infrastruktur. Die Application-Schicht arbeitet nur mit Schnittstellen. Dadurch können Abhängigkeiten leicht ersetzt werden (z. B. EF Core durch Dapper).


Abhängigkeitsverwaltung zwischen den Schichten

Abhängigkeiten werden typischerweise über Dependency Injection (DI) in der Infrastructure- oder Composition Root-Schicht aufgelöst:


// Program.cs oder Startup.cs
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddScoped<ApproveOrderUseCase>();

Der Controller kennt nur ApproveOrderUseCase und weiß nicht, welche Repository-Implementierung verwendet wird.


Unterschiede zwischen Schichten- und Clean Architecture

Merkmal Klassische Schichtenarchitektur Clean Architecture
Abhängigkeitsrichtung Von oben nach unten Von außen nach innen (zum Kern)
Datenbankabhängigkeit Geschäftslogik hängt von der Datenbank ab Domain ist unabhängig von der Datenzugriffsschicht
Testbarkeit Schwierig Einfach (Mock-Schnittstellen)
Änderbarkeit Eng gekoppelte Schichten Abhängigkeiten durch Abstraktionen gesteuert
Langzeitwartung Wird mit der Zeit schwieriger Einfach zu warten, modulare Struktur

Praxisbeispiel: E-Commerce-Anwendung

Unten sehen Sie eine vereinfachte Struktur eines Clean Architecture-basierten E-Commerce-Systems:

Mit dieser Struktur kann die UI-Schicht (z. B. API vs. WPF) geändert werden, ohne dass die Geschäftslogik (Domain & Application) angepasst werden muss.


Best Practices


Beispielhafte Projektstruktur


MyApp/
├── MyApp.Domain/
│   ├── Entities/
│   ├── Interfaces/
│   └── Exceptions/
├── MyApp.Application/
│   ├── UseCases/
│   ├── Services/
│   └── DTOs/
├── MyApp.Infrastructure/
│   ├── Persistence/
│   └── Repositories/
└── MyApp.API/
    ├── Controllers/
    └── Program.cs

Diese Struktur entspricht der Clean Architecture und bietet eine modulare, leicht wartbare .NET-Lösung.


TL;DR

  • Schichtenarchitektur: Klassisches Modell mit UI → Business → Data-Fluss.
  • Clean Architecture: Kehrt die Abhängigkeiten um – der Kern ist die Domain.
  • Domain ist unabhängig, Application steuert den Ablauf, Infrastructure kümmert sich um externe Systeme.
  • Dependency Injection ist der Schlüssel zur Abhängigkeitsverwaltung.
  • Jede Schicht sollte eine klare Verantwortung haben, testbar und austauschbar sein.
  • Clean Architecture ist ideal für langlebige, wartbare und skalierbare Systeme.

Ähnliche Artikel