Communitygithub.com

Domain-Driven Design – Patterns & Practices

Bereitstellung von Best Practices und gebrauchsfertigen Entwicklungsskills für Domain-Driven Design zur Unterstützung Ihres KI-Assistenten.

Was ist Domain-Driven Design – Patterns & Practices?

Diese Skill-Ressource für KI-Assistenten stellt eine umfassende deutsche Anleitung für Domain-Driven Design (DDD) bereit. Sie behandelt sowohl strategische als auch taktische DDD-Konzepte wie Bounded Contexts, Context Maps, Entities, Value Objects, Aggregates, Domain Events, Domain Services und Repositories. Die Skill-Ressource zeigt anhand von TypeScript-Codebeispielen, wie Anti-Patterns wie das anämische Domänenmodell oder zu große Aggregate vermieden werden. Zusätzlich enthält sie eine vollständige Verzeichnisstruktur nach DDD-Prinzipien, die Clean-Architecture-Dependency-Rule, sowie Hinweise zur Fehlerbehebung und Validierung. Entwickler können diese Beschreibung nutzen, um KI-Assistenten wie Claude Code, Cursor oder Codex zu instruieren, in Projekten mit komplexer Geschäftslogik DDD-konforme Architekturen zu entwerfen und umzusetzen.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add kok-o/koko-contextos-agents

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Domain-Driven Design — Patterns & Practices

Wann DDD verwenden

Verwenden, wenn:

  • Komplexe Geschäftslogik über CRUD hinaus
  • Mehrere Fachexperten mit unterschiedlichen Vokabularen
  • Das Domänenmodell ist der Wettbewerbsvorteil
  • Unternehmenskritische Anwendungen

Nicht verwenden, wenn:

  • Einfache CRUD-Anwendungen
  • Hackathon/MVP (Overkill)
  • Kein Fachexperte verfügbar

Strategisches Design

Bounded Contexts

Das wichtigste DDD-Konzept. Ein Bounded Context ist eine Grenze, innerhalb derer ein bestimmtes Modell definiert und anwendbar ist.

Beispiel — E-Commerce:

[Bestellkontext]          [Zahlungskontext]       [Versandkontext]
  - Bestellung               - Zahlung               - Sendung
  - Bestellposition          - Transaktion            - Sendungsnummer
  - Kunde (Ref)              - Rückerstattung         - Adresse
  - Adresse (Wert)           - Rechnung               - Spediteur

Kunde bedeutet in jedem Kontext etwas Anderes:

  • Bestellkontext: Name, E-Mail, Versandpräferenz
  • Zahlungskontext: Rechnungsdaten, Zahlungsmethoden
  • Support-Kontext: Ticketverlauf, Zufriedenheitswert

Kontextkarte

[Bestellung] ←→ [Zahlung]     # Partnerschaft
[Bestellung] →  [Versand]     # Kunde-Lieferant
[Bestellung] →  [Legacy CRM]   # Anti-Corruption Layer

Taktisches Design

Entitäten

Objekte mit Identität. Zwei Entitäten mit denselben Attributen aber unterschiedlichen IDs sind unterschiedlich.

class User {
  readonly id: UserId;
  name: string;
  email: Email;  // Value Object
}

Value Objects

Objekte, die durch ihre Attribute definiert werden, nicht durch Identität. Unveränderlich.

class Email {
  constructor(readonly value: string) {
    if (!isValidEmail(value)) throw new InvalidEmailError(value);
  }
  equals(other: Email): boolean {
    return this.value === other.value;
  }
}

Aggregate

Ein Verbund von Entitäten und Value Objects mit einer einzelnen Wurzelentität (Aggregate Root). Jeder Zugriff erfolgt über die Wurzel.

class Order {  // Aggregate Root
  private items: OrderItem[] = [];
  
  addItem(product: ProductRef, quantity: number): void {
    // Geschäftslogik HIER, nicht in einem Service
    if (quantity <= 0) throw new InvalidQuantityError();
    this.items.push(new OrderItem(product, quantity));
  }
  
  get total(): Money {
    return this.items.reduce((sum, item) => sum.add(item.subtotal), Money.zero());
  }
}

Aggregat-Regeln:

  1. Verweise auf andere Aggregate nur per ID
  2. Ein Aggregat pro Transaktion
  3. Eventuelle Konsistenz zwischen Aggregaten

Domain Events

Etwas, das in der Domäne passiert ist und für Fachexperten relevant ist.

class OrderPlaced implements DomainEvent {
  constructor(
    readonly orderId: OrderId,
    readonly customerId: CustomerId,
    readonly total: Money,
    readonly occurredAt: Date
  ) {}
}

Domain Services

Geschäftslogik, die natürlicherweise nicht zu einer Entität oder einem Value Object gehört.

class PricingService {
  calculatePrice(order: Order, customer: Customer, promotions: Promotion[]): Money {
    // Komplexe Preislogik unter Einbeziehung mehrerer Aggregate
  }
}

Repositories

Abstraktion des Datenzugriffs. Ein Repository pro Aggregate Root.

interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  save(order: Order): Promise<void>;
  delete(id: OrderId): Promise<void>;
}

Verzeichnisstruktur (DDD)

src/
├── modules/
│   └── orders/                    # Bounded Context
│       ├── domain/
│       │   ├── entities/
│       │   │   └── order.ts       # Aggregate Root
│       │   ├── value-objects/
│       │   │   └── money.ts
│       │   ├── events/
│       │   │   └── order-placed.ts
│       │   ├── services/
│       │   │   └── pricing.ts
│       │   └── repositories/
│       │       └── order.repository.ts  # Interface
│       ├── application/
│       │   ├── commands/
│       │   │   └── place-order.ts
│       │   ├── queries/
│       │   │   └── get-order.ts
│       │   └── handlers/
│       │       └── place-order.handler.ts
│       └── infrastructure/
│           ├── persistence/
│           │   └── order.repository.impl.ts  # Implementierung
│           └── api/
│               └── orders.controller.ts

Die Clean-Architecture-Abhängigkeitsregel

In DDD MÜSSEN Abhängigkeiten streng nach innen zeigen:

[ Frameworks & Treiber (Web, DB, UI) ]
      └──▶ [ Interface Adapter (Controller, Gateways) ]
            └──▶ [ Anwendung (Use Cases, CQRS Handler) ]
                  └──▶ [ Domäne (Entitäten, Value Objects) ]
  • Die Domänenschicht hat NULL Abhängigkeiten von ORMs (Prisma, TypeORM), HTTP-Frameworks (Express, NestJS) oder externen SDKs.
  • Repositories werden als Interfaces in der Domänen-/Anwendungsschicht definiert und in der Infrastrukturschicht implementiert.

Domain Events vs. Integration Events

  1. Domain Events: Stellen Zustandsänderungen innerhalb eines einzelnen Bounded Context dar.
    • Werden direkt innerhalb des Aggregate Root ausgelöst (order.addItem(...) löst OrderItemAdded aus).
    • Werden prozessintern vor dem Transaktions-Commit ausgeliefert.
  2. Integration Events: Werden über Bounded-Context-Grenzen hinweg veröffentlicht, um mit anderen Diensten zu kommunizieren.
    • Werden über das Transactional-Outbox-Muster an Message Broker gesendet.
    • Müssen rückwärtskompatible Schemas mit Versionierung verwenden.

Anti-Corruption Layer (ACL)

Wenn Daten aus einem externen Bounded Context oder einer Drittanbieter-API (z. B. Stripe, Salesforce) konsumiert werden:

  • NIEMALS externe Domänenmodelle direkt in die eigene Domäne importieren.
  • Einen ACL-Übersetzer/Adapter in der Infrastrukturschicht erstellen, um externe DTOs in die eigenen Value Objects und Entitäten zu konvertieren.

Anti-Patterns

  • [FAIL] Anämisches Domänenmodell – Entitäten haben nur Getter/Setter, alle Logik in Services
  • [FAIL] Großes Aggregat – Aggregate sollten klein sein und sich auf Invarianten konzentrieren
  • [FAIL] Aggregat-übergreifende Transaktionen – eventual consistency verwenden
  • [FAIL] DDD überall – DDD nur dort einsetzen, wo die Komplexität es rechtfertigt
  • [FAIL] ORM-Entitäten, die in die Domäne auslaufen – Domänenentitäten dürfen nicht von @Entity() oder ORM-Dekorateuren abhängen

Codebeispiele

Siehe EXAMPLES.md für detaillierte Codebeispiele.

Validierungs-Checkliste

Was während der Überprüfungsphase vor Abschluss der Aufgabe zu prüfen ist.

Häufige Fehler

Anti-Patterns und Dinge, die explizit vermieden werden sollten. Siehe TROUBLESHOOTING.md.

Integrationshinweise

Wie dieser Skill mit anderen Skills interagiert.

ddd Fehlerbehebung & Häufige Fehler

1. God-Aggregate

  • Symptom: Der Aggregate Root enthält 20 Kind-Entitäten und das Laden erfordert das Verknüpfen Dutzender Tabellen.
  • Ursache: ERD-Tabellen als Aggregatgrenzen behandeln, anstatt als transaktionale Konsistenzeinheiten.
  • Lösung: Kleine Aggregate entwerfen. Auf andere Aggregate nur per ID verweisen, nicht per Objektreferenz.

2. Infrastruktur in die Domänenschicht auslaufen lassen

  • Symptom: Domänenentitäten importieren Prisma, TypeORM-Dekorateure oder Express-Request-Objekte.
  • Ursache: Clean-Architecture-Grenzen umkehren.
  • Lösung: Die Domänenschicht muss reines TypeScript ohne externe Framework-Abhängigkeiten sein.

3. Transaktion, die mehrere Aggregate umspannt

  • Symptom: Hohe Datenbank-Sperrkonflikte und Deadlocks bei parallelen Transaktionen.
  • Ursache: Mehrere Aggregate Roots innerhalb derselben Datenbanktransaktion ändern.
  • Lösung: Faustregel: Genau ein Aggregate Root pro Transaktion ändern. Domain Events für eventual consistency zwischen anderen Aggregaten verwenden.

Verwandte Skills