Aggregater, repositories og factories
Beskytt invariants med aggregater og gjør dem persistente på en ryddig måte.
Aggregater, repositories og factories er en gratis leksjon i PHP Academy på CoddyKit. Dette er leksjon 2 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i PHP Academy, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i PHP Academy inneholder totalt 4 leksjoner.
Beskytte invarianter
Når De har entiteter og verdiobjekter, trenger De mønstre for å holde klynger av dem konsistente og for å persisttere dem på en ryddig måte. DDD svarer med tre taktiske mønstre: aggregatet (en konsistensgrense), repositoryet (en samlingslignende persistensabstraksjon) og fabrikken (for kompleks opprettelse). Denne leksjonen viser hvordan de passer sammen i PHP.
Hva er et aggregat
Et aggregat er en klynge av entiteter og verdiobjekter som behandles som én enhet ved dataendringer. Én entitet er aggregatroten – det eneste medlemmet som kode utenfra kan holde en referanse til. Alle endringer går gjennom roten, som håndhever aggregatets invarianter. Aggregatet er også transaksjonsgrensen: det lastes inn og lagres atomisk.
Roten beskytter helheten
Kallere utenfra berører aldri indre medlemmer direkte. For å legge til en ordrelinje kaller De en metode på roten, som validerer og opprettholder konsistensen (totaler, grenser). Slik holdes invariantene samlet på ett sted.
<?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
Utforming av små aggregater
En vanlig feil er å gjøre aggregater for store, for eksempel en Order som også eier hele Customer-grafen. Tommelfingerregler:
- Hold aggregater små; referer til andre aggregater med id i stedet for å holde på objektet.
- Én transaksjon bør endre ett aggregat; koordiner på tvers av aggregater med domenebegivenheter.
- Invarianter som alltid må være oppfylt, definerer grensen.
Referanse med id
Ordren lagrer et customerId-verdiobjekt, ikke en Customer-entitet. Dette holder konsistensgrensen avgrenset og unngår innlasting av enorme objektgrafer. Konsistens på tvers av aggregater oppnås etter hvert og håndteres av hendelser i stedet for én gigantisk transaksjon.
<?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;
Repository-kontrakten
Et repository gir illusjonen av en samling aggregatrøtter i minnet. Domenet er bare avhengig av grensesnittet; implementasjonen (Doctrine, PDO eller minnebasert) ligger i infrastrukturlaget. Repositories arbeider med hele aggregater, aldri delvise rader.
<?php
interface OrderRepository {
public function ofId(string $id): ?Order;
public function save(Order $order): void;
public function nextIdentity(): string;
}
En implementasjon i minnet
Et minnebasert repository er uvurderlig for raske enhetstester uten database. Fordi domenet er avhengig av grensesnittet, kan De fritt bytte implementasjon (Dependency Inversion i praksis).
<?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);
Repository er ikke en DAO
Et repository er ikke en generisk CRUD-DAO. Det tilbyr domeneorienterte spørringer (findOverdueOrders()) og rekonstruerer fullstendige aggregater med invariantene intakt. Det skjuler bevisst SQL- og ORM-detaljer, slik at domenet forblir uavhengig av persistens. Unngå å lekke spørringsbyggere eller generiske metoder som save($anyEntity) inn i domenet.
Fabrikker for kompleks opprettelse
Når opprettelsen av et aggregat innebærer reell logikk – generering av identitet, sammensetting av verdiobjekter og håndheving av invarianter ved opprettelse – bør De flytte den til en fabrikk (en egen klasse eller en statisk navngitt konstruktør). Dette holder konstruktøren ryddig og samler reglene for gyldig opprettelse på ett sted.
<?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;
Slik samarbeider de
Den typiske flyten i en applikasjonstjeneste:
- En fabrikk eller navngitt konstruktør oppretter et gyldig aggregat.
- Metodene på aggregatroten håndhever invariantene under bruk.
- Et repository lagrer og rekonstruerer senere hele aggregatet.
Applikasjonstjenesten orkestrerer dette i én transaksjon per aggregat og er bare avhengig av grensesnitt.
Håndheve en invariant for hele aggregatet
Rotens virkelige verdi er at den håndhever invarianter som gjelder på tvers av medlemmene. Her avviser ordren en ordrelinje hvis den ville føre totalen over en kredittgrense – en regel som ingen enkelt LineItem kunne håndheve alene. Fordi alle endringer går gjennom roten, kan regelen aldri omgås.
<?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
Hurtigsjekk
Utforming av aggregater.
Oppsummering
De har lært å beskytte invarianter og håndtere persistens på en ryddig måte. Aggregater danner en konsistens- og transaksjonsgrense, endres bare gjennom roten og holdes små ved at andre aggregater refereres til med id. Repositories presenterer aggregatrøtter som en samling bak et domenegrensesnitt, skjuler ORM/SQL og muliggjør minnebaserte testdobler. Fabrikker samler kompleks opprettelse som håndhever invarianter. Til sammen holder de domenemodellen konsistent og uavhengig av persistens.
Lær deg PHP med en AI-veileder – gratis
Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.
- Kurs
- 49
- Leksjoner
- 195
Ofte stilte spørsmål
Er leksjonen «Aggregater, repositories og factories» gratis?
Ja – hele teksten i «Aggregater, repositories og factories» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av PHP Academy-kurset, kan du oppgradere til CoddyKit PRO. Kurset i PHP Academy inneholder totalt 4 leksjoner.
Hva lærer jeg i «Aggregater, repositories og factories»?
Beskytt invariants med aggregater og gjør dem persistente på en ryddig måte. Du øver på PHP Academy med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.
Trenger jeg erfaring for å begynne med PHP Academy?
Ingen tidligere erfaring er nødvendig. PHP Academy på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.
Hvor lang tid tar leksjonen «Aggregater, repositories og factories»?
De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.
Kan jeg skrive og kjøre kode i denne PHP Academy-leksjonen?
Ja. Alle PHP Academy-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.
Alle leksjonene i dette kurset
- Byggeklosser i DDD: entiteter og verdiobjekter
- Aggregater, repositories og factories
- Domenebegivenheter og domenetjenester
- Bounded contexts og kontekstkartlegging