Agregaty, repozytoria i fabryki
Proszę chronić niezmienniki za pomocą agregatów i poprawnie je utrwalać.
Agregaty, repozytoria i fabryki to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 2 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.
Ochrona niezmienników
Po wprowadzeniu encji i obiektów wartości potrzebne są wzorce, które pozwalają zachować spójność ich grup oraz czysto je utrwalać. DDD odpowiada trzema wzorcami taktycznymi: Agregatem (granicą spójności), Repozytorium (abstrakcją trwałości przypominającą kolekcję) i Fabryką (do złożonego tworzenia). W tej lekcji pokazano, jak współdziałają one w PHP.
Czym jest agregat
Agregat to grupa encji i obiektów wartości traktowana jako jedna jednostka podczas modyfikowania danych. Jedna encja jest korzeniem agregatu — jedynym elementem, do którego kod zewnętrzny może mieć referencję. Wszystkie modyfikacje przechodzą przez korzeń, który egzekwuje niezmienniki agregatu. Agregat jest również granicą transakcji: jest ładowany i zapisywany atomowo.
Korzeń chroni całość
Kod zewnętrzny nigdy nie odwołuje się bezpośrednio do elementów wewnętrznych. Aby dodać element zamówienia, wywołuje się metodę korzenia, która sprawdza poprawność i utrzymuje spójność (sumy, limity). Dzięki temu niezmienniki znajdują się w jednym miejscu.
<?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
Projektowanie małych agregatów
Częstym błędem jest tworzenie zbyt dużych agregatów (na przykład Order, który posiada również cały graf Customer). Przydatne zasady:
- Agregaty powinny być małe; do innych agregatów należy odwoływać się za pomocą identyfikatora, zamiast przechowywać obiekt.
- Jedna transakcja powinna modyfikować jeden agregat; zmiany między agregatami należy koordynować za pomocą zdarzeń domenowych.
- Granicę wyznaczają niezmienniki, które muszą być zawsze spełnione.
Odwołanie za pomocą identyfikatora
Zamówienie przechowuje obiekt wartości customerId, a nie encję Customer. Dzięki temu granica spójności pozostaje niewielka i nie trzeba ładować ogromnych grafów obiektów. Spójność między agregatami staje się ostateczna i jest obsługiwana przez zdarzenia, a nie jedną gigantyczną transakcję.
<?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;
Kontrakt repozytorium
Repozytorium stwarza wrażenie kolekcji korzeni agregatów znajdującej się w pamięci. Domena zależy wyłącznie od interfejsu, a implementacja (Doctrine, PDO, pamięciowa) znajduje się w warstwie infrastruktury. Repozytoria operują na całych agregatach, nigdy na częściowych wierszach.
<?php
interface OrderRepository {
public function ofId(string $id): ?Order;
public function save(Order $order): void;
public function nextIdentity(): string;
}
Implementacja w pamięci
Repozytorium w pamięci jest nieocenione przy szybkich testach jednostkowych niewymagających bazy danych. Ponieważ domena zależy od interfejsu, można swobodnie wymieniać implementacje (odwrócenie zależności w praktyce).
<?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);
Repozytorium to nie DAO
Repozytorium nie jest ogólnym DAO CRUD. Udostępnia zapytania mające znaczenie domenowe (findOverdueOrders()) i odtwarza pełne agregaty z zachowanymi niezmiennikami. Celowo ukrywa szczegóły SQL i ORM, aby domena pozostała niezależna od sposobu utrwalania danych. Należy unikać wyciekania konstruktorów zapytań lub ogólnych metod save($anyEntity) do domeny.
Fabryki do złożonego tworzenia
Gdy tworzenie agregatu obejmuje rzeczywistą logikę — generowanie tożsamości, składanie obiektów wartości i egzekwowanie niezmienników obowiązujących podczas tworzenia — należy przenieść ją do Fabryki (wydzielonej klasy lub statycznego konstruktora nazwanego). Dzięki temu konstruktor encji pozostaje prosty, a reguły poprawnego tworzenia są scentralizowane.
<?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;
Jak współpracują
Typowy przebieg w serwisie aplikacyjnym:
- Fabryka (lub konstruktor nazwany) tworzy poprawny agregat.
- Metody korzenia agregatu egzekwują niezmienniki podczas korzystania z agregatu.
- Repozytorium utrwala cały agregat, a później go odtwarza.
Serwis aplikacyjny koordynuje te działania w ramach jednej transakcji na agregat i zależy wyłącznie od interfejsów.
Egzekwowanie niezmiennika całego agregatu
Rzeczywista wartość korzenia polega na egzekwowaniu niezmienników obejmujących wiele elementów. W tym przypadku zamówienie odrzuca element zamówienia, jeśli jego dodanie spowodowałoby przekroczenie limitu kredytowego — reguły, której pojedynczy LineItem nie mógłby samodzielnie egzekwować. Ponieważ wszystkie zmiany przechodzą przez korzeń, nie można ominąć tej reguły.
<?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
Szybki test
Projektowanie agregatów.
Podsumowanie
Poznali Państwo sposoby ochrony niezmienników i czystego utrwalania danych. Agregaty wyznaczają granicę spójności i transakcji; można je modyfikować wyłącznie przez ich korzeń, a ich niewielki rozmiar utrzymuje się przez odwoływanie do innych agregatów za pomocą identyfikatorów. Repozytoria udostępniają korzenie agregatów jako kolekcję za pośrednictwem interfejsu domenowego, ukrywając ORM/SQL i umożliwiając użycie atrap testowych w pamięci. Fabryki centralizują złożone tworzenie, które egzekwuje niezmienniki. Razem pomagają utrzymać model domenowy spójny i niezależny od sposobu utrwalania danych.
Często zadawane pytania
Czy lekcja „Agregaty, repozytoria i fabryki” jest bezpłatna?
Tak — pełny tekst „Agregaty, repozytoria i fabryki” 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 „Agregaty, repozytoria i fabryki”?
Proszę chronić niezmienniki za pomocą agregatów i poprawnie je utrwalać. Ć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 2 z 4.
Ile czasu zajmuje lekcja „Agregaty, repozytoria i fabryki”?
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