Domain Modeling, Use Cases & Invarianten

Dieses Kapitel zeigt, wie aus Fachlichkeit belastbare Java-Modelle entstehen: Value Objects, Aggregates, Policies, Events und Commands.

DDDUse CasesInvariantenRecords
Domain Modeling mit Commands, Use Case, Aggregate, Repository und Event
Domain Modeling mit Commands, Use Case, Aggregate, Repository und Event

Fachbegriffe

Der größte Fehler in Enterprise-Projekten ist oft nicht technisch, sondern sprachlich: Kunde, Auftrag, Bestellung, Rechnung, Reservierung und Zahlung werden in jedem Team anders verstanden. Ein Domain Model zwingt das Team zu präzisen Begriffen.

Invarianten

InvarianteBeispielOrt im Code
Eine Bestellung braucht mindestens eine PositionLeere Bestellung ist ungültigOrder.place
Menge muss positiv seinquantity > 0OrderLine.of
Stornierte Order darf nicht fakturiert werdenStatuswechsel blockierenOrder.cancel / BillingPolicy
Event beschreibt fachliche TatsacheOrderPlaced nach erfolgreicher ErzeugungDomainEvent

Value Objects

Value Objects reduzieren Primitive Obsession. Statt überall String customerId und BigDecimal amount zu verwenden, entstehen Typen mit Regeln.

Value Object mit Invariante
public record Money(BigDecimal amount, Currency currency) {
    public Money {
        if (amount == null || currency == null) throw new IllegalArgumentException("Money requires amount and currency");
        if (amount.scale() > 2) throw new IllegalArgumentException("Money supports cents only");
    }

    public Money plus(Money other) {
        if (!currency.equals(other.currency)) throw new IllegalArgumentException("Currency mismatch");
        return new Money(amount.add(other.amount), currency);
    }
}

Aggregate

Ein Aggregate ist keine technische Entity-Sammlung. Es ist eine fachliche Konsistenzgrenze. Im Beispiel darf nur Order entscheiden, ob Positionen, Status und Events zusammen gültig sind.

Events

Ein Domain Event beschreibt, was fachlich passiert ist. Es ist nicht primär ein technischer Kafka-Record, sondern eine Wahrheit aus der Fachlichkeit.

Beispiel

Aggregate schützt Invarianten
public final class Order {
    private final OrderId id;
    private final CustomerId customerId;
    private final List<OrderLine> lines = new ArrayList<>();
    private final List<DomainEvent> events = new ArrayList<>();
    private OrderStatus status;

    private Order(OrderId id, CustomerId customerId) {
        this.id = id;
        this.customerId = customerId;
        this.status = OrderStatus.NEW;
    }

    // Pattern: Factory Method - erzeugt nur fachlich gültige Orders.
    public static Order place(CustomerId customerId, List<OrderLine> requestedLines) {
        if (requestedLines == null || requestedLines.isEmpty()) {
            throw new DomainRuleViolation("Eine Bestellung braucht mindestens eine Position");
        }
        Order order = new Order(OrderId.newId(), customerId);
        requestedLines.forEach(order::addLine);
        order.events.add(new OrderPlaced(order.id, customerId));
        return order;
    }

    public void cancel(String reason) {
        if (status == OrderStatus.INVOICED) {
            throw new DomainRuleViolation("Fakturierte Bestellung kann nicht storniert werden");
        }
        status = OrderStatus.CANCELLED;
        events.add(new OrderCancelled(id, reason));
    }
}
⌂ Cockpit