PHP Academy · Lektion

Aggregates, Repositories und Factories

Schützen Sie Invarianten mit Aggregates und persistieren Sie diese sauber.

Lektion 2 von 413 Schritte

Aggregates, Repositories und Factories ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 2 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des PHP Academy-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Invarianten schützen

Sobald Sie Entitäten und Wertobjekte haben, benötigen Sie Muster, um Gruppen aus ihnen konsistent zu halten und sie sauber zu persistieren. DDD bietet dafür drei taktische Muster: das Aggregat (eine Konsistenzgrenze), das Repository (eine sammlungsähnliche Persistenzabstraktion) und die Factory (für komplexe Erstellung). Diese Lektion zeigt, wie sie in PHP zusammenspielen.

Was ist ein Aggregat

Ein Aggregat ist eine Gruppe aus Entitäten und Wertobjekten, die bei Datenänderungen als eine Einheit behandelt wird. Eine Entität ist die Aggregatwurzel – das einzige Mitglied, auf das Code außerhalb des Aggregats eine Referenz halten darf. Alle Änderungen laufen über die Wurzel, die die Invarianten des Aggregats durchsetzt. Das Aggregat ist außerdem die Transaktionsgrenze: Es wird atomar geladen und gespeichert.

Die Wurzel schützt das Ganze

Aufrufer außerhalb des Aggregats greifen niemals direkt auf innere Mitglieder zu. Um eine Auftragsposition hinzuzufügen, rufen Sie eine Methode der Wurzel auf, die die Gültigkeit prüft und die Konsistenz (Summen, Limits) sicherstellt. So liegen die Invarianten an einer einzigen Stelle.

<?php
final class LineItem {
    public function __construct(
        public readonly string $sku,
        public readonly int $qty,
        public readonly int $unitCents
    ) {}
    public function subtotal(): int { return $this->qty * $this->unitCents; }
}

final class Order { // Aggregate Root
    /** @var LineItem[] */
    private array $items = [];
    public function __construct(public readonly string $id) {}

    public function addItem(string $sku, int $qty, int $unitCents): void {
        if ($qty < 1) { throw new DomainException('qty must be >= 1'); }
        $this->items[] = new LineItem($sku, $qty, $unitCents);
    }
    public function total(): int {
        return array_sum(array_map(fn(LineItem $i) => $i->subtotal(), $this->items));
    }
}
$o = new Order('o1');
$o->addItem('A', 2, 500);
$o->addItem('B', 1, 300);
echo $o->total(), PHP_EOL; // 1300

Kleine Aggregate entwerfen

Ein häufiger Fehler besteht darin, Aggregate zu groß zu machen, etwa ein Order, das auch den vollständigen Customer-Graphen besitzt. Als Faustregeln gelten:

  • Halten Sie Aggregate klein; referenzieren Sie andere Aggregate über ihre ID, statt das Objekt zu halten.
  • Eine Transaktion sollte ein Aggregat ändern; koordinieren Sie Änderungen über Aggregate hinweg mit Domänenereignissen.
  • Invarianten, die jederzeit gelten müssen, legen die Grenze fest.

Über die ID referenzieren

Das Auftrag hält ein customerId-Wertobjekt und keine Customer-Entität. Dadurch bleibt die Konsistenzgrenze eng und das Laden großer Objektgraphen wird vermieden. Die Konsistenz über Aggregatgrenzen hinweg wird schließlich hergestellt und durch Ereignisse statt durch eine einzige große Transaktion behandelt.

<?php
final class CustomerId {
    public function __construct(public readonly string $value) {}
}
final class Order {
    public function __construct(
        public readonly string $id,
        public readonly CustomerId $customerId // reference, not object
    ) {}
}
$order = new Order('o1', new CustomerId('cus_99'));
echo $order->customerId->value, PHP_EOL;

Der Repository-Vertrag

Ein Repository vermittelt den Eindruck einer In-Memory-Sammlung von Aggregatwurzeln. Die Domäne hängt nur von der Schnittstelle ab; die Implementierung (Doctrine, PDO, In-Memory) liegt in der Infrastrukturschicht. Repositories arbeiten mit vollständigen Aggregaten und niemals mit Teilmengen von Zeilen.

<?php
interface OrderRepository {
    public function ofId(string $id): ?Order;
    public function save(Order $order): void;
    public function nextIdentity(): string;
}

Eine In-Memory-Implementierung

Ein In-Memory-Repository ist für schnelle, datenbankfreie Unit-Tests äußerst wertvoll. Da die Domäne von der Schnittstelle abhängt, können Sie Implementierungen frei austauschen (Dependency Inversion in Aktion).

<?php
interface OrderRepository {
    public function ofId(string $id): ?object;
    public function save(object $order): void;
    public function nextIdentity(): string;
}
final class Order { public function __construct(public readonly string $id) {} }

final class InMemoryOrderRepository implements OrderRepository {
    private array $store = [];
    public function ofId(string $id): ?object { return $this->store[$id] ?? null; }
    public function save(object $order): void { $this->store[$order->id] = $order; }
    public function nextIdentity(): string { return 'o_' . bin2hex(random_bytes(4)); }
}
$repo = new InMemoryOrderRepository();
$repo->save(new Order('o1'));
var_dump($repo->ofId('o1') !== null);

Ein Repository ist kein DAO

Ein Repository ist kein generisches CRUD-DAO. Es stellt fachlich aussagekräftige Abfragen bereit (findOverdueOrders()) und stellt vollständige Aggregate mit intakten Invarianten wieder her. Es verbirgt SQL- und ORM-Details bewusst, damit die Domäne persistenzunabhängig bleibt. Vermeiden Sie es, Query Builder oder generische Methoden wie save($anyEntity) in die Domäne durchsickern zu lassen.

Factories für komplexe Erstellung

Wenn die Erstellung eines Aggregats echte Logik umfasst – etwa das Erzeugen der Identität, das Zusammensetzen von Wertobjekten oder das Durchsetzen von Invarianten bei der Erstellung –, verlagern Sie sie in eine Factory (eine eigene Klasse oder einen statischen benannten Konstruktor). So bleibt der Konstruktor der Entität schlank und die Regeln für eine gültige Erstellung sind zentral gebündelt.

<?php
final class Order {
    private function __construct(
        public readonly string $id,
        public readonly string $customerId
    ) {}
    public static function place(string $customerId): self {
        if ($customerId === '') { throw new DomainException('customer required'); }
        return new self('o_' . bin2hex(random_bytes(4)), $customerId);
    }
}
$order = Order::place('cus_1');
echo $order->id, PHP_EOL;

So arbeiten sie zusammen

Der typische Ablauf in einem Anwendungsdienst:

  • Eine Factory (oder ein benannter Konstruktor) erstellt ein gültiges Aggregat.
  • Die Methoden der Aggregatwurzel setzen die Invarianten während der Verwendung durch.
  • Ein Repository persistiert das vollständige Aggregat und stellt es später wieder her.

Der Anwendungsdienst koordiniert diese Schritte innerhalb einer Transaktion pro Aggregat und hängt dabei nur von Schnittstellen ab.

Eine Invariante für das gesamte Aggregat durchsetzen

Der eigentliche Wert der Wurzel besteht darin, Invarianten durchzusetzen, die mehrere Mitglieder betreffen. In diesem Beispiel weist der Auftrag eine Auftragsposition zurück, wenn sie den Gesamtbetrag über ein Kreditlimit heben würde – eine Regel, die kein einzelnes LineItem allein durchsetzen könnte. Da alle Änderungen über die Wurzel erfolgen, kann die Regel niemals umgangen werden.

<?php
final class Order {
    private array $items = [];
    public function __construct(
        public readonly string $id,
        private int $creditLimitCents
    ) {}
    public function addItem(int $cents): void {
        if ($this->total() + $cents > $this->creditLimitCents) {
            throw new DomainException('Exceeds credit limit');
        }
        $this->items[] = $cents;
    }
    public function total(): int { return array_sum($this->items); }
}
$o = new Order('o1', 1000);
$o->addItem(600);
try { $o->addItem(600); } catch (DomainException $e) { echo $e->getMessage(), PHP_EOL; }
echo $o->total(), PHP_EOL; // 600

Schnelltest

Aggregatentwurf.

Zusammenfassung

Sie haben gelernt, Invarianten zu schützen und sauber zu persistieren. Aggregate bilden eine Konsistenz- und Transaktionsgrenze, werden nur über ihre Wurzel geändert und bleiben klein, indem andere Aggregate über ihre ID referenziert werden. Repositories stellen Aggregatwurzeln über eine Domänenschnittstelle als Sammlung bereit, verbergen ORM und SQL und ermöglichen In-Memory-Test-Doubles. Factories bündeln die komplexe Erstellung, bei der Invarianten durchgesetzt werden. Zusammen halten sie Ihr Domänenmodell konsistent und persistenzunabhängig.

Kostenlos starten

Lerne PHP mit einem KI-Tutor — kostenlos

Schreibe und führe echten Code in deinem Browser aus, bekomme sofortige Hilfe von einem 24/7 KI-Tutor und setze dein Lernen im Web oder in der App fort.

Kurse
49
Lektionen
195

Häufig gestellte Fragen

Ist die Lektion „Aggregates, Repositories und Factories“ kostenlos?

Ja — der vollständige Text von „Aggregates, Repositories und Factories“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des PHP Academy-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der PHP Academy-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Aggregates, Repositories und Factories“?

Schützen Sie Invarianten mit Aggregates und persistieren Sie diese sauber. Du übst PHP Academy mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um PHP Academy zu starten?

Keine Vorkenntnisse erforderlich. PHP Academy auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 2 von 4.

Wie lange dauert die Lektion „Aggregates, Repositories und Factories“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser PHP Academy-Lektion Code schreiben und ausführen?

Ja. Jede PHP Academy-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. DDD-Bausteine: Entities und Value Objects
  2. Aggregates, Repositories und Factories
  3. Domain Events und Domain Services
  4. Bounded Contexts und Context Mapping
← Zurück zu PHP Academy