Domain-Driven Design: Software nach der Domäne | Enjyn Gruppe
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

235 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

Enjix Beta

Enjyn AI Agent

Hallo 👋 Ich bin Enjix — wie kann ich dir helfen?
120