Skalierung von DDD in ASP.NET: Herausforderungen über 30 Aggregaten
Wenn ein Projekt mehr als 30 Aggregaten in ASP.NET mit Entity Framework Core erreicht, führt das Prinzip »eine Abfrage – ein Aggregat« zu gravierenden Leistungsproblemen. Jedes Aggregat muss vollständig geladen werden, inklusive verschachtelter Objekte, was SQL-Abfragen mit zahlreichen JOINs erzeugt. Dies wird besonders kritisch in ERP-Systemen oder Marktplätzen, wo Objektgraphen bis zu 5–6 Ebenen tief sind.
Ein typischer Query für ein Order-Aggregat sieht so aus:
var orders = await context.Orders
.Include(o => o.Customer)
.ThenInclude(c => c.Address)
.Include(o => o.Customer)
.ThenInclude(c => c.Contacts)
.Include(o => o.Items)
.ThenInclude(i => i.Product)
.ThenInclude(p => p.Category)
.Include(o => o.Items)
.ThenInclude(i => i.Discount)
.ToListAsync();
Die resultierende SQL-Abfrage enthält Dutzende LEFT JOINs und verlangsamt die Ausführung deutlich. Ändert man nur ein Feld im Auftragskopf, müssen alle OrderItems als Teil des Aggregats geladen werden – was die Leistung weiter belastet.
Explosive Zunahme von Spezifikationen und ihre Grenzen
Das Specification-Muster kapselt Abfrage-Logik, doch bei über 30 Aggregaten kann die Sammlung von Specification-Klassen auf Hunderte Dateien anwachsen. Die Kombination mittels And/Or führt aufgrund der IQueryable-Abhängigkeit von EF Core zu ineffizientem SQL.
Wesentliche Herausforderungen:
- ORM-spezifisch: Spezifikationen funktionieren nicht mit Dapper oder gRPC ohne Neuschreibung.
- Komplexe Zusammensetzung: Realwelt-Szenarien mit Rollen, Status und Zeitzone werden unübersichtlich.
- Abstraktionsaufwand: Die Zeit für Abstraktionen übersteigt oft die Implementierung der eigentlichen Geschäftslogik.
Transaktionen über Aggregatgrenzen hinweg und Infrastruktur-Overhead
Der scoped DbContext in ASP.NET Core funktioniert gut für Unit-of-Work in kleinen Projekten. In großen Systemen erfordern Geschäftsabläufe oft gleichzeitige Updates über 5+ Aggregaten: Auftrag platzieren, Zahlung verarbeiten, Lager reservieren, Call-Center-Aufgabe erstellen.
Verfügbare Lösungen widersprechen DDD-Prinzipien:
- Einzelne Datenbank-Transaktion – führt zu God-Services und bricht Aggregat-Grenzen.
- Verteilte Sagas oder Outbox-Patterns – erhöhen die Komplexität um bis zu 10-fach.
Als Folge entfällt 70 % des Codebases auf Infrastruktur-Code zur Synchronisation, nicht auf Geschäftslogik.
Code-Navigation: Tausende Dateien und verlangsamte Iterationen
Die DDD-Struktur (API/Application/Domain/Infrastructure + Features) erzeugt bei über 30 Aggregaten mehr als 1.000 Dateien. Ändert man ein einzelnes Entitätsfeld, wirkt sich dies auf folgende Elemente aus:
- Domain-Entität
- Fluent-API-Konfiguration
- Migration
- Command
- Validator (FluentValidation)
- Handler
- DTO
- Mapper (AutoMapper)
- Specification
- Tests
Dies verlangsamt die Entwicklung stark, erschwert Onboarding und behindert die langfristige Wartung.
Was zählt wirklich
- Leistung: Schwere Include/ThenInclude-Operationen erzeugen ineffizientes SQL und lassen API-Antwortzeiten exponentiell steigen.
- Spezifikationen: Skalieren nicht, sind an EF Core gebunden und ihre Zusammensetzung führt zu Fehlern.
- Transaktionen: Über-Aggregate-Operationen erzwingen Kompromisse zwischen DDD-Ideen und realen Geschäftsanforderungen.
- Codebase-Größe: Mehr als 1.000 Dateien verlangsamen Iterationen um 50–70 %.
- Hybrider Ansatz: DDD nur im Kern anwenden; CQRS und Vertical Slices für Nebenbereiche nutzen.
Empfehlungen für die Migration
Für Projekte mit über 30 Aggregaten hybride Architekturen einführen:
- CQRS: Befehle von Abfragen trennen; Lese-Modelle ohne Include-Klauseln verwenden.
- Vertical Slices: Code nach Funktion gruppieren, um Schichtenkomplexität zu minimieren.
- Feature-Sliced Design: Modularität erreichen, ohne starre, Zwiebel-artige Architektur einzuführen.
Diese Ansätze bewahren DDD-Prinzipien in komplexen Domänen, vereinfachen aber alles andere.
— Editorial Team
Noch keine Kommentare.