Zdarzenia domenowe i usługi domenowe
Proszę rejestrować fakty biznesowe za pomocą zdarzeń i usług domenowych.
Zdarzenia domenowe i usługi domenowe to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.
Rejestrowanie faktów biznesowych
Nie każde zachowanie mieści się w jednej encji. Zdarzenia domenowe rejestrują istotne fakty biznesowe, które już zaszły (OrderPlaced), dzięki czemu inne części systemu mogą na nie reagować. Serwisy domenowe zawierają logikę domenową, która nie należy w naturalny sposób do jednej encji ani obiektu wartości. W tej lekcji pokazano, jak oba te elementy pozwalają zachować ekspresyjność modelu bez nadmiernego rozbudowywania agregatów.
Czym jest zdarzenie domenowe
Zdarzenie domenowe jest niezmiennym zapisem czegoś, co wydarzyło się w domenie, nazwanym w czasie przeszłym z użyciem wszechobecnego języka: OrderPlaced, PaymentReceived, CustomerRelocated. Zawiera dane opisujące fakt oraz czas jego wystąpienia. Jest obiektem wartości: tworzy się je raz i nigdy nie zmienia.
<?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;
Agregaty rejestrują zdarzenia
Korzeń agregatu rejestruje zdarzenia jako część swojego zachowania, a następnie udostępnia je, aby warstwa aplikacji mogła je wysłać po zatwierdzeniu transakcji. Sam agregat nie wysyła zdarzeń — jedynie je rejestruje. Dzięki temu domena pozostaje wolna od kwestii infrastruktury.
<?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; }
Wysyłanie po zatwierdzeniu
Serwis aplikacyjny utrwala agregat, zatwierdza transakcję, a następnie wysyła zarejestrowane zdarzenia. Wysyłanie po zatwierdzeniu gwarantuje, że handlery nigdy nie zareagują na fakt, który został wycofany. Poniższa kolejność jest bezpiecznym schematem dla efektów ubocznych, takich jak wiadomości e-mail czy wywołania zewnętrzne.
<?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;
Handlery reagują
Każde zdarzenie może mieć wiele handlerów niezależnych od agregatu, który je wywołał. Dispatcher PSR-14 kieruje zdarzenie do zarejestrowanych listenerów. Dodanie nowej reakcji (analityki, powiadomienia) oznacza dodanie listenera, a nie modyfikowanie agregatu.
<?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'));
Zdarzenia między granicami
Zdarzenia domenowe rozdzielają agregaty, które muszą zachować spójność, ale nie powinny współdzielić transakcji. OrderPlaced w kontekście Sales może wywołać rezerwację zapasów w innym kontekście za pośrednictwem zdarzenia. Wewnątrz systemu są one obiektami, a między usługami często stają się zdarzeniami integracyjnymi przesyłanymi przez brokera komunikatów. Należy rozróżniać wewnętrzne zdarzenia domenowe i zewnętrzne zdarzenia integracyjne — publiczny kontrakt nie powinien być modelem wewnętrznym.
Czym jest serwis domenowy
Serwis domenowy zawiera logikę domenową, która:
- obejmuje wiele agregatów lub encji, więc nie pasuje do żadnej z nich, oraz
- jest rzeczywistym pojęciem domenowym wyrażonym we wszechobecnym języku.
Jest bezstanowy i działa na obiektach domenowych. Klasycznym przykładem jest serwis FundsTransfer, który obciąża jedno konto i uznaje drugie.
Serwis domenowy w kodzie
Serwis koordynuje dwa agregaty, ale nie przechowuje własnego stanu. Logika nie należy do żadnego z kont, dlatego znajduje się w serwisie nazwanym od operacji domenowej.
<?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
Serwis domenowy a serwis aplikacyjny
Nie należy ich mylić:
- Serwis domenowy zawiera logikę biznesową i znajduje się w warstwie domenowej; nie wie nic o transakcjach ani HTTP.
- Serwis aplikacyjny koordynuje przypadek użycia: ładuje agregaty z repozytoriów, wywołuje logikę domenową, zatwierdza transakcję i wysyła zdarzenia. Jest cienkim koordynatorem, który nie zawiera własnych reguł biznesowych.
Unikaj pułapki serwisów
Serwisy domenowe łatwo nadużywać. Jeśli logika może znajdować się w encji lub obiekcie wartości, powinna tam trafić — właśnie tak unika się anemicznego modelu. Po serwis domenowy należy sięgać tylko wtedy, gdy operacja rzeczywiście obejmuje wiele agregatów lub nie ma naturalnego miejsca. W przeciwnym razie zachowanie zostanie usunięte z encji, a kod proceduralny odtworzony w innym miejscu.
Serwis aplikacyjny koordynuje
Składając wszystko w całość: cienki serwis aplikacyjny ładuje agregaty z repozytoriów, wywołuje serwis domenowy w celu obsługi logiki obejmującej wiele agregatów, zapisuje wynik i wysyła zdarzenia. Zawiera wyłącznie orkiestrację — nie zawiera własnych reguł biznesowych.
<?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
Szybki test
Zdarzenia i serwisy.
Podsumowanie
Ujęli Państwo fakty biznesowe i logikę, której wcześniej nie przypisano właściwemu miejscu. Zdarzenia domenowe to niezmienne zapisy sformułowane w czasie przeszłym, które agregaty rejestrują, a warstwa aplikacji wysyła po zatwierdzeniu, oddzielając reakcje i przekraczając granice agregatów. Serwisy domenowe zawierają bezstanową logikę domenową obejmującą wiele agregatów, w odróżnieniu od cienkich serwisów aplikacyjnych, które jedynie orkiestrują przypadki użycia. Należy używać obu oszczędnie, aby zachowanie pozostawało w encjach i obiektach wartości, czyli tam, gdzie powinno się znajdować.
Ucz się PHP dzięki korepetycjom AI — za darmo
Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.
- Kursy
- 49
- Lekcje
- 195
Często zadawane pytania
Czy lekcja „Zdarzenia domenowe i usługi domenowe” jest bezpłatna?
Tak — pełny tekst „Zdarzenia domenowe i usługi domenowe” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Zdarzenia domenowe i usługi domenowe”?
Proszę rejestrować fakty biznesowe za pomocą zdarzeń i usług domenowych. Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć PHP Academy?
Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.
Ile czasu zajmuje lekcja „Zdarzenia domenowe i usługi domenowe”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?
Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Elementy DDD: encje i obiekty wartości
- Agregaty, repozytoria i fabryki
- Zdarzenia domenowe i usługi domenowe
- Konteksty ograniczone i mapowanie kontekstów