Domain-Driven Design in der Praxis cover image

Domain-Driven Design in der Praxis

Von Julian Krause · Samstag, 10. Mai 2025 · ~3 Min. Lesezeit

Domain-Driven Design gibt es seit über zwei Jahrzehnten, dennoch tun sich die meisten Teams noch immer schwer, es wirksam einzusetzen. Nachdem ich jahrelang DDD in mehreren Enterprise-Projekten umgesetzt habe, möchte ich praktische Erkenntnisse teilen, die über die typischen „Aggregate-Root“-Tutorials hinausgehen.

Beginnen Sie mit dem Problem, nicht mit dem Muster

Der größte Fehler, den ich bei Teams beobachte, ist der Griff zu DDD-Mustern, bevor die Domäne überhaupt verstanden wurde. Sie brauchen am ersten Tag keine Aggregates, Value Objects und Domain Events. Sie brauchen Gespräche mit Fachexperten.

In einem kürzlichen Logistikprojekt haben wir drei Wochen mit Event-Storming-Sitzungen verbracht, bevor auch nur eine Zeile Code geschrieben wurde. Diese Sitzungen zeigten, dass „Sendung“ für das Lagerteam und das Abrechnungsteam etwas völlig anderes bedeutete — eine Erkenntnis, die unmittelbar unsere Bounded-Context-Grenzen beeinflusste.

Bounded Contexts sind entscheidend

Die richtigen Bounded Contexts zu finden ist wichtiger, als das perfekte Aggregate-Design zu erreichen. Ein gut abgegrenzter Bounded Context mit mittelmäßigem Innenleben schlägt jedes Mal einen schlecht abgegrenzten Context mit makellosem Aggregate-Design.

// Shipping context - Shipment is the aggregate root
public class Shipment
{
    public ShipmentId Id { get; private set; }
    public Address Origin { get; private set; }
    public Address Destination { get; private set; }
    public ShipmentStatus Status { get; private set; }
    private readonly List<ShipmentEvent> _events = new();

    public void Dispatch(CarrierId carrierId, DateTime estimatedDelivery)
    {
        if (Status != ShipmentStatus.Pending)
            throw new InvalidOperationException("Only pending shipments can be dispatched.");

        Status = ShipmentStatus.InTransit;
        _events.Add(new ShipmentDispatched(Id, carrierId, estimatedDelivery));
    }
}

// Billing context - same real-world shipment, different model
public class BillableShipment
{
    public BillableShipmentId Id { get; private set; }
    public CustomerId CustomerId { get; private set; }
    public Money ShippingCost { get; private set; }
    public BillingStatus BillingStatus { get; private set; }
}

Beachten Sie, wie dasselbe reale Konzept zwei völlig unterschiedliche Darstellungen hat. Das ist keine Duplizierung, sondern saubere Trennung der Zuständigkeiten.

Value Objects verdienen mehr Aufmerksamkeit

Teams überspringen Value Objects oft, weil sie wie unnötiges Zeremoniell wirken. In der Praxis gehören sie zu den wertvollsten DDD-Mustern überhaupt. Ein gut entworfenes Value Object eliminiert ganze Kategorien von Fehlern.

public record EmailAddress
{
    public string Value { get; }

    public EmailAddress(string value)
    {
        if (string.IsNullOrWhiteSpace(value))
            throw new ArgumentException("Email address cannot be empty.");

        if (!value.Contains('@') || !value.Contains('.'))
            throw new ArgumentException($"'{value}' is not a valid email address.");

        Value = value.ToLowerInvariant().Trim();
    }

    public static implicit operator string(EmailAddress email) => email.Value;
}

Jedes Mal, wenn Sie einen string weiterreichen, der eine E-Mail-Adresse, eine URL oder eine Telefonnummer repräsentiert, verpassen Sie die Gelegenheit, Domänenregeln auf Typebene zu verankern.

Domain Events für kontextübergreifende Kommunikation

Domain Events sind der sauberste Weg, zwischen Bounded Contexts zu kommunizieren, ohne enge Kopplung zu erzeugen. Wir verwenden MediatR für prozessinterne Events und einen Message Broker für dienstübergreifende Events.

public class OrderPlacedHandler : INotificationHandler<OrderPlaced>
{
    private readonly IInventoryService _inventory;
    private readonly INotificationService _notifications;

    public async Task Handle(OrderPlaced notification, CancellationToken ct)
    {
        await _inventory.ReserveStockAsync(notification.Items, ct);
        await _notifications.SendOrderConfirmationAsync(notification.CustomerId, ct);
    }
}

Praktische Erkenntnisse

Nach Jahren der DDD-Anwendung ist mein wichtigster Rat einfach: Investieren Sie viel Zeit in das Verständnis der Domäne, bevor Sie zu Mustern greifen. Nutzen Sie Bounded Contexts, um Komplexität zu beherrschen. Verlassen Sie sich auf Value Objects für Typsicherheit. Und setzen Sie Domain Events ein, um Ihre Contexts entkoppelt zu halten.

Bei DDD geht es nicht um die Muster. Es geht darum, Software zu bauen, die das zu lösende Geschäftsproblem präzise abbildet.


Kommentare (3)

Hannah Donnerstag, 26. Februar 2026 17:08

I have a question: does this also apply to older versions?

Clara Freitag, 27. Februar 2026 20:08

Good question, I'd like to know too.

David Samstag, 7. März 2026 23:08

Exactly! I had the same thought.

Anna Freitag, 27. Februar 2026 17:08

Thanks for sharing — very helpful.

Ben Samstag, 28. Februar 2026 17:08

Could you elaborate on this topic in a follow-up post?

Kommentar hinterlassen