Event Sourced Aggregates
An aggregate is a domain object whose state is rebuilt by replaying its event history. Aggregates enforce business rules before new events are written to a stream — they are the write-side counterpart to projections.
The pattern
- Load every event for the aggregate's stream.
- Apply each event to rebuild the current state.
- Invoke a command method on the aggregate.
- The aggregate enforces invariants and emits zero or more new events.
- The new events are appended to the stream and broadcast to projections.
Example
final class BasketAggregate extends Aggregate implements IsAggregate
{
public function __construct(
private AggregateId $id,
private Price $totalAmount,
) {
parent::__construct();
}
public function addItemToBasket(ProductId $productId, ItemName $name, Price $amount): void
{
if ($this->totalAmount->exceeds(Price::limit())) {
throw new BasketLimitReached();
}
$this->addNewEvent(new ItemAddedToBasket($productId, $name, $amount));
}
protected function applyItemAddedToBasket(ItemAddedToBasket $event): void
{
$this->totalAmount = $this->totalAmount->plus($event->amount);
}
}
Loading an aggregate
$events = $client->readStream(StreamId::fromString('basket-42'));
$basket = BasketAggregate::reconstitute($events);
$basket->addItemToBasket($productId, $name, $price);
$client->append($basket->pullNewEvents());
Combine aggregates (write model) with projections (read model) to get a clean CQRS architecture. See CQRS Scaffolding for tooling that generates the boilerplate for you.