Domain Events und Domain Services
Erfassen Sie geschäftliche Fakten mit Domain Events und Domain Services.
Domain Events und Domain Services ist eine kostenlose PHP Academy-Lektion auf CoddyKit. Dies ist Lektion 3 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.
Geschäftliche Fakten festhalten
Nicht jedes Verhalten lässt sich in eine einzelne Entität einordnen. Domänenereignisse halten bedeutende geschäftliche Fakten fest, die bereits eingetreten sind (OrderPlaced), sodass andere Teile des Systems darauf reagieren können. Domänendienste enthalten Domänenlogik, die nicht auf natürliche Weise zu einer einzelnen Entität oder einem Wertobjekt gehört. Diese Lektion zeigt, wie beide Ihr Modell ausdrucksstark halten, ohne Aggregate aufzublähen.
Was ist ein Domänenereignis
Ein Domänenereignis ist ein unveränderlicher Datensatz über etwas, das in der Domäne geschehen ist. Es wird mithilfe der allgegenwärtigen Sprache in der Vergangenheitsform benannt: OrderPlaced, PaymentReceived, CustomerRelocated. Es enthält die Daten, die den Sachverhalt beschreiben, sowie den Zeitpunkt seines Eintretens. Es ist ein Wertobjekt: Es wird einmal erstellt und danach nie verändert.
<?php
final class OrderPlaced {
public function __construct(
public readonly string $orderId,
public readonly int $totalCents,
public readonly DateTimeImmutable $occurredOn = new DateTimeImmutable()
) {}
}
$e = new OrderPlaced('o1', 1300);
echo $e->orderId, ' @ ', $e->occurredOn->format('H:i'), PHP_EOL;
Aggregate zeichnen Ereignisse auf
Die Aggregatwurzel zeichnet Ereignisse als Teil ihres Verhaltens auf und stellt sie anschließend bereit, damit die Anwendungsschicht sie nach dem Commit der Transaktion dispatchen kann. Das Aggregat selbst dispatcht nichts – es zeichnet lediglich auf. So bleibt die Domäne frei von Infrastrukturbelangen.
<?php
final class OrderPlaced {
public function __construct(public readonly string $orderId) {}
}
abstract class AggregateRoot {
private array $events = [];
protected function record(object $e): void { $this->events[] = $e; }
public function releaseEvents(): array {
$out = $this->events; $this->events = []; return $out;
}
}
final class Order extends AggregateRoot {
public function __construct(public readonly string $id) {
$this->record(new OrderPlaced($id));
}
}
$o = new Order('o1');
foreach ($o->releaseEvents() as $e) { echo get_class($e), PHP_EOL; }
Nach dem Commit dispatchen
Der Anwendungsdienst persistiert das Aggregat, führt den Commit aus und dispatcht anschließend die freigegebenen Ereignisse. Durch das Dispatchen nach dem Commit reagieren Handler niemals auf einen Sachverhalt, der zurückgerollt wurde. Die folgende Reihenfolge ist das sichere Muster für Seiteneffekte wie E-Mails oder externe Aufrufe.
<?php
final class PlaceOrderService {
public function __construct(
private object $repo,
private object $dispatcher
) {}
public function handle(object $order): void {
// 1. persist inside a transaction
$this->repo->save($order); // commit happens here
// 2. only AFTER commit, release and dispatch
foreach ($order->releaseEvents() as $event) {
$this->dispatcher->dispatch($event);
}
}
}
echo "order persisted, then events dispatched", PHP_EOL;
Handler reagieren
Für jedes Ereignis kann es viele Handler geben, die vom Aggregat entkoppelt sind, das es ausgelöst hat. Ein PSR-14-Dispatcher leitet das Ereignis an registrierte Listener weiter. Eine neue Reaktion (etwa für Analytics oder Benachrichtigungen) hinzuzufügen bedeutet, einen Listener hinzuzufügen, ohne das Aggregat anzufassen.
<?php
final class OrderPlaced { public function __construct(public readonly string $orderId) {} }
final class Dispatcher {
private array $listeners = [];
public function on(string $event, callable $l): void { $this->listeners[$event][] = $l; }
public function dispatch(object $event): void {
foreach ($this->listeners[$event::class] ?? [] as $l) { $l($event); }
}
}
$d = new Dispatcher();
$d->on(OrderPlaced::class, fn($e) => print("email for {$e->orderId}\n"));
$d->on(OrderPlaced::class, fn($e) => print("analytics for {$e->orderId}\n"));
$d->dispatch(new OrderPlaced('o1'));
Ereignisse über Grenzen hinweg
Domänenereignisse entkoppeln Aggregate, die konsistent bleiben müssen, ohne eine gemeinsame Transaktion zu verwenden. OrderPlaced im Sales-Kontext kann über ein Ereignis die Reservierung von Bestand in einem anderen Kontext auslösen. Intern sind diese Ereignisse Objekte; über Servicegrenzen hinweg werden sie häufig zu Integrationsereignissen auf einem Message Broker. Halten Sie interne Domänenereignisse und externe Integrationsereignisse getrennt – der öffentliche Vertrag sollte nicht Ihr internes Modell sein.
Was ist ein Domänendienst
Ein Domänendienst enthält Domänenlogik, die:
- mehrere Aggregate oder Entitäten betrifft und daher in keines von ihnen passt, und
- ein echter Domänenbegriff ist, der in der allgegenwärtigen Sprache ausgedrückt wird.
Er ist zustandslos und arbeitet mit Domänenobjekten. Ein FundsTransfer-Dienst, der ein Konto belastet und einem anderen Konto Geld gutschreibt, ist das klassische Beispiel.
Ein Domänendienst im Code
Der Dienst koordiniert zwei Aggregate, besitzt aber keinen eigenen Zustand. Die Logik gehört zu keinem der beiden Konten und lebt daher in einem Dienst, der nach dem Domänenvorgang benannt ist.
<?php
final class Account {
public function __construct(public readonly string $id, private int $cents) {}
public function withdraw(int $c): void {
if ($c > $this->cents) { throw new DomainException('insufficient funds'); }
$this->cents -= $c;
}
public function deposit(int $c): void { $this->cents += $c; }
public function balance(): int { return $this->cents; }
}
final class MoneyTransferService {
public function transfer(Account $from, Account $to, int $cents): void {
$from->withdraw($cents);
$to->deposit($cents);
}
}
$a = new Account('a', 1000); $b = new Account('b', 0);
(new MoneyTransferService())->transfer($a, $b, 400);
echo $a->balance(), '/', $b->balance(), PHP_EOL; // 600/400
Domänendienst oder Anwendungsdienst
Verwechseln Sie die beiden nicht:
- Ein Domänendienst enthält Geschäftslogik und ist in der Domänenschicht angesiedelt; er weiß nichts über Transaktionen oder HTTP.
- Ein Anwendungsdienst koordiniert einen Anwendungsfall: Er lädt Aggregate aus Repositories, ruft Domänenlogik auf, führt die Transaktion aus und dispatcht Ereignisse. Er ist ein schlanker Koordinator ohne eigene Geschäftsregeln.
Die Service-Falle vermeiden
Domänendienste werden leicht übermäßig eingesetzt. Wenn Logik auf einer Entität oder einem Wertobjekt liegen kann, sollte sie dort liegen: So vermeiden Sie ein anämisches Modell. Verwenden Sie einen Domänendienst nur, wenn der Vorgang tatsächlich mehrere Aggregate umfasst oder keinen natürlichen Ort hat. Andernfalls entziehen Sie Ihren Entitäten Verhalten und schreiben prozeduralen Code erneut.
Application Service koordiniert
Zusammengefasst: Ein schlanker Application Service lädt Aggregate aus Repositories, ruft für Logik über mehrere Aggregate hinweg einen Domain Service auf, speichert das Ergebnis und versendet Events. Er enthält ausschließlich Orchestrierungslogik – keine eigenen Geschäftsregeln.
<?php
final class Account {
public function __construct(public readonly string $id, public int $cents) {}
}
final class MoneyTransferService { // domain service: the rule
public function transfer(Account $from, Account $to, int $c): void {
if ($c > $from->cents) { throw new DomainException('insufficient'); }
$from->cents -= $c; $to->cents += $c;
}
}
final class TransferApp { // application service: orchestration only
public function __construct(private MoneyTransferService $domain) {}
public function run(Account $a, Account $b, int $c): void {
$this->domain->transfer($a, $b, $c); // then repo->save + dispatch
}
}
$a = new Account('a', 1000); $b = new Account('b', 0);
(new TransferApp(new MoneyTransferService()))->run($a, $b, 250);
echo $a->cents, '/', $b->cents, PHP_EOL; // 750/250
Kurztest
Events und Services.
Zusammenfassung
Sie haben fachliche Fakten und verwaiste Logik erfasst. Domain Events sind unveränderliche Aufzeichnungen in der Vergangenheitsform, die Aggregate aufzeichnen und die Anwendungsschicht nach dem Commit versendet. Dadurch werden Reaktionen entkoppelt und Grenzen zwischen Aggregaten überschritten. Domain Services enthalten zustandslose fachliche Logik, die sich über mehrere Aggregate erstreckt. Sie unterscheiden sich von schlanken Application Services, die lediglich Use Cases orchestrieren. Setzen Sie beide sparsam ein, damit das Verhalten dort bleibt, wo es hingehört: in Entities und Value Objects.
Häufig gestellte Fragen
Ist die Lektion „Domain Events und Domain Services“ kostenlos?
Ja — der vollständige Text von „Domain Events und Domain Services“ 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 „Domain Events und Domain Services“?
Erfassen Sie geschäftliche Fakten mit Domain Events und Domain Services. 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 3 von 4.
Wie lange dauert die Lektion „Domain Events und Domain Services“?
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
- DDD-Bausteine: Entities und Value Objects
- Aggregates, Repositories und Factories
- Domain Events und Domain Services
- Bounded Contexts und Context Mapping