0Pricing
PHP Academy · Lekcja

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

  1. Elementy DDD: encje i obiekty wartości
  2. Agregaty, repozytoria i fabryki
  3. Zdarzenia domenowe i usługi domenowe
  4. Konteksty ograniczone i mapowanie kontekstów
← Powrót do PHP Academy