Hallo Welt
Hallo Welt
Original Lingva Deutsch
Übersetzung wird vorbereitet...
Dieser Vorgang kann bis zu 60 Sekunden dauern.
Diese Seite wird erstmalig übersetzt und dann für alle Besucher gespeichert.
0%
DE Zurück zu Deutsch
Übersetzung durch Lingva Translate

234 Dokumentationen verfügbar

Wissensdatenbank

Domain Driven Design Grundlagen

Zuletzt aktualisiert: 20.01.2026 um 10:03 Uhr

Domain-Driven Design: Software nach der Domäne

DDD richtet Software-Architektur an der Geschäftsdomäne aus. Lernen Sie die Grundkonzepte für komplexe Anwendungen.

Kernkonzepte

Domain-Driven Design
├── Strategisches Design
│   ├── Bounded Contexts
│   ├── Ubiquitous Language
│   └── Context Maps
│
└── Taktisches Design
    ├── Entities
    ├── Value Objects
    ├── Aggregates
    ├── Repositories
    └── Domain Services

Ubiquitous Language

# Gemeinsame Sprache zwischen Entwicklern und Domain-Experten

❌ Entwickler-Sprache:
"Der UserEntity wird in die OrderTable inserted"

✅ Ubiquitous Language:
"Der Kunde platziert eine Bestellung"

# Im Code spiegeln:
class Customer { }           // Nicht: UserEntity
class Order { }              // Nicht: OrderRecord
customer.placeOrder(items)   // Nicht: user.insert(data)

Entities vs. Value Objects

Entity Value Object
Hat Identität (ID) Keine Identität
Mutable (veränderbar) Immutable (unveränderlich)
Gleichheit durch ID Gleichheit durch Werte
Beispiel: User, Order Beispiel: Address, Money
// Entity - hat ID, Gleichheit durch ID
class Customer {
    constructor(
        public readonly id: CustomerId,
        private name: string,
        private email: Email
    ) {}

    equals(other: Customer): boolean {
        return this.id.equals(other.id);
    }

    changeName(newName: string): void {
        // Validierung
        if (newName.length < 2) throw new Error('Name too short');
        this.name = newName;
    }
}

// Value Object - immutable, Gleichheit durch Werte
class Money {
    constructor(
        public readonly amount: number,
        public readonly currency: string
    ) {
        if (amount < 0) throw new Error('Amount cannot be negative');
    }

    equals(other: Money): boolean {
        return this.amount === other.amount
            && this.currency === other.currency;
    }

    add(other: Money): Money {
        if (this.currency !== other.currency) {
            throw new Error('Currency mismatch');
        }
        return new Money(this.amount + other.amount, this.currency);
    }
}

class Address {
    constructor(
        public readonly street: string,
        public readonly city: string,
        public readonly postalCode: string,
        public readonly country: string
    ) {}

    equals(other: Address): boolean {
        return this.street === other.street
            && this.city === other.city
            && this.postalCode === other.postalCode
            && this.country === other.country;
    }
}

Aggregates

// Aggregate = Cluster von Entities/Value Objects
// Eine Entity ist der Aggregate Root

/*
 Order Aggregate:
 ┌─────────────────────────────────────┐
 │  Order (Aggregate Root)             │
 │  ├── OrderId                        │
 │  ├── Customer (Reference)           │
 │  ├── OrderItems[]                   │
 │  │   ├── ProductId                  │
 │  │   ├── Quantity                   │
 │  │   └── Price (Money)              │
 │  └── ShippingAddress (Value Object) │
 └─────────────────────────────────────┘

 Regeln:
 - Zugriff nur über Aggregate Root
 - Transaktionsgrenzen = Aggregate-Grenzen
 - Referenz auf andere Aggregates nur per ID
*/

class Order {
    private items: OrderItem[] = [];

    constructor(
        public readonly id: OrderId,
        private customerId: CustomerId,  // Referenz per ID!
        private shippingAddress: Address
    ) {}

    addItem(productId: ProductId, quantity: number, price: Money): void {
        // Geschäftsregel: Max 20 Items pro Bestellung
        if (this.items.length >= 20) {
            throw new Error('Maximum items reached');
        }
        this.items.push(new OrderItem(productId, quantity, price));
    }

    removeItem(productId: ProductId): void {
        this.items = this.items.filter(i => !i.productId.equals(productId));
    }

    get total(): Money {
        return this.items.reduce(
            (sum, item) => sum.add(item.subtotal),
            new Money(0, 'EUR')
        );
    }
}

Repositories

// Repository = Persistenz-Abstraktion für Aggregates

interface OrderRepository {
    findById(id: OrderId): Promise<Order | null>;
    save(order: Order): Promise<void>;
    findByCustomer(customerId: CustomerId): Promise<Order[]>;
}

class PostgresOrderRepository implements OrderRepository {
    async findById(id: OrderId): Promise<Order | null> {
        const row = await this.db.query(
            'SELECT * FROM orders WHERE id = $1',
            [id.value]
        );
        if (!row) return null;
        return this.toDomain(row);
    }

    async save(order: Order): Promise<void> {
        // Upsert Order + OrderItems in Transaction
        await this.db.transaction(async (tx) => {
            await tx.query(
                'INSERT INTO orders ... ON CONFLICT UPDATE ...',
                this.toRow(order)
            );
            // Items speichern...
        });
    }

    private toDomain(row: any): Order {
        // Mapping von DB zu Domain
    }
}

Bounded Contexts

/*
 E-Commerce System

 ┌─────────────────┐    ┌─────────────────┐
 │  Sales Context  │    │ Shipping Context│
 │                 │    │                 │
 │  Order          │    │  Shipment       │
 │  Customer       │    │  Delivery       │
 │  Product        │    │  Address        │
 └────────┬────────┘    └────────┬────────┘
          │                      │
          └──────────────────────┘
                   │
                   ▼
          Context Map (Integration)

 - Gleiche Begriffe, andere Bedeutung!
 - "Order" in Sales ≠ "Order" in Shipping
 - Jeder Context hat eigene Modelle
*/

Domain Services

// Domain Service = Logik die nicht zu einer Entity gehört

class PricingService {
    calculatePrice(
        product: Product,
        customer: Customer,
        quantity: number
    ): Money {
        let price = product.basePrice.multiply(quantity);

        // Mengenrabatt
        if (quantity > 10) {
            price = price.multiply(0.9);
        }

        // Kundenrabatt
        if (customer.isPremium) {
            price = price.multiply(0.95);
        }

        return price;
    }
}

// Application Service orchestriert
class OrderApplicationService {
    constructor(
        private orderRepo: OrderRepository,
        private pricingService: PricingService
    ) {}

    async placeOrder(command: PlaceOrderCommand): Promise<OrderId> {
        const price = this.pricingService.calculatePrice(
            command.product,
            command.customer,
            command.quantity
        );

        const order = new Order(/*...*/);
        order.addItem(command.productId, command.quantity, price);

        await this.orderRepo.save(order);
        return order.id;
    }
}
💡 Wann DDD? DDD lohnt sich bei komplexer Business-Logik. Für einfache CRUD-Apps ist es Overkill.

Weitere Informationen