Domain-Driven Design in C#: Wie man in der Komplexität nicht untergeht

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Domain-Driven Design in C#: Wie man in der Komplexität nicht untergeht

\n\n

Wenn Ihr C#-Projekt die Phase der "einfachen CRUD-Anwendung" hinter sich lässt, kommt der Moment der Wahrheit. Die Geschäftslogik beginnt, sich in die Quere zu kommen, die Methoden der Dienste wachsen auf Hunderte von Zeilen an, und das Wort "Refactoring" löst nervöses Zittern aus. Kommt Ihnen das bekannt vor? Willkommen in der Welt, in der Sie ohne Domain-Driven Design (DDD) riskieren, Ihren Code in einen Sumpf zu verwandeln.

\n\n

DDD geht nicht um "ein weiteres Framework". Es ist eine Philosophie, die die Geschäftsdomäne in den Mittelpunkt des Universums stellt. Heute werden wir uns ansehen, wie Sie Domain-Driven Design in C#-Projekten so anwenden, dass der Code nicht nur funktioniert, sondern verständlich und flexibel ist. Kein überflüssiges Gerede – nur echte Beispiele und praktische Tipps.

\n\n

Warum rettet DDD C#-Projekte?

\n\n

Stellen Sie sich vor, Sie schreiben ein System zur Verwaltung von Bestellungen in einem Online-Shop. Wenn Sie einfach die gesamte Logik in ASP.NET-Controller packen, werden Sie nach einem halben Jahr nicht mehr erklären können, warum die Methode CreateOrder() die Methode CalculateShipping() aufruft, die wiederum für Wechselkurse in die Datenbank greift. DDD bietet eine klare Trennung: Es gibt eine Domänenschicht, in der die Geschäftslogik lebt, und eine Infrastruktur, die sich um technische Details kümmert.

\n\n

Die Schlüsselidee von DDD ist die Ubiquitous Language (allgegenwärtige Sprache). Sie und die Fachexperten sprechen dieselbe Sprache. Im Code drückt sich dies in Begriffen wie "Order", "LineItem", "DiscountPolicy" aus, nicht in "OrderDto", "OrderService" und "Helper". Je näher der Code an der Geschäftssprache ist, desto einfacher ist er zu warten.

\n\n

Strategisches und taktisches Design

\n\n

DDD unterteilt sich in zwei Teile: den strategischen (wie man das System in Module aufteilt) und den taktischen (wie man den Code innerhalb eines Moduls schreibt). In C# bietet uns das taktische Design vier mächtige Bausteine:

\n\n
    \n
  • Entity – ein Objekt mit einer eindeutigen Kennung (z. B. OrderId).
  • \n
  • Value Object – ein Objekt, das durch seine Attribute definiert wird (z. B. Money oder Address).
  • \n
  • Aggregate Root – die Wurzelentität, die eine Gruppe von Objekten verwaltet.
  • \n
  • Repository – eine Abstraktion des Datenzugriffs.
  • \n
\n\n

Schauen wir uns an, wie das im Code aussieht.

\n\n

Beispiel: Entwurf einer Bestellung (Order)

\n\n

Beginnen wir einfach: Wir haben eine Bestellung, die aus Positionen besteht. Eine Bestellung kann den Status "Neu", "Bezahlt" oder "Storniert" haben.

\n\n

1. Value Object: Money

\n\n

Geld ist ein klassisches Value Object. Zwei Money-Objekte sind gleich, wenn sie denselben Betrag und dieselbe Währung haben. Sie sind unveränderlich (immutable).

\n\n
public sealed record Money(decimal Amount, string Currency)\n{\n    public static Money operator +(Money left, Money right)\n    {\n        if (left.Currency != right.Currency)\n            throw new InvalidOperationException("Currency mismatch");\n        return new Money(left.Amount + right.Amount, left.Currency);\n    }\n}\n
\n\n

Beachten Sie das record – in C# 9+ ist dies die ideale Möglichkeit, ein Value Object zu erstellen. Es liefert automatisch Equals, GetHashCode und Unveränderlichkeit.

\n\n

2. Entity: Order und LineItem

\n\n

Entitäten haben eine Kennung. Erstellen wir eine Basisklasse für Entity:

\n\n
public abstract class Entity<TId>\n{\n    public TId Id { get; protected set; }\n\n    protected Entity(TId id) => Id = id;\n\n    public override bool Equals(object? obj) =>\n        obj is Entity<TId> other && Id!.Equals(other.Id);\n\n    public override int GetHashCode() => Id!.GetHashCode();\n}\n
\n\n

Jetzt die Order und LineItem selbst:

\n\n
public class Order : Entity<Guid>\n{\n    private readonly List<LineItem> _items = new();\n    public IReadOnlyList<LineItem> Items => _items.AsReadOnly();\n    public OrderStatus Status { get; private set; }\n    public Money Total { get; privat                

Blogs

Buchempfehlungen