Агрегат - група пов'язаних об'єктів, яку розглядають як одне ціле для змін. Один з об'єктів - корінь агрегату: лише через нього зовнішній код змінює агрегат.
Приклад: замовлення (Order - корінь) з позиціями (OrderLine). Позицію не змінюють напряму - лише через замовлення:
$order->addLine($product, quantity: 2);
$order->changeQuantity($lineId, 5);
$order->removeLine($lineId);
Навіщо - інваріанти. Інваріант - бізнес-правило, яке має виконуватися завжди:
- сума замовлення дорівнює сумі позицій;
- у замовленні не більше 50 позицій;
- оплачене замовлення не можна змінювати;
- загальна знижка не перевищує 30%.
Якщо позиції можна змінювати напряму (OrderLine::find($id)->update(...)), правила перевіряються в кількох місцях - або не перевіряються. Корінь агрегату - єдине місце, де їх контролюють:
public function addLine(Product $product, int $quantity): void
{
if ($this->isPaid()) {
throw new OrderIsLocked($this);
}
if ($this->lines->count() >= 50) {
throw new TooManyLines($this);
}
$this->lines->push(new OrderLine(...));
$this->recalculateTotal();
}
Межа агрегату - межа транзакції. Агрегат зберігається й змінюється атомарно, в одній транзакції. Зміна двох агрегатів в одній транзакції - сигнал перевірити межі.
Правила проєктування (Вон Вернон):
- маленькі агрегати: лише те, що потрібно для інваріантів. «Клієнт з усіма замовленнями» - поганий агрегат: зміна одного замовлення блокує всі;
- посилання на інші агрегати - за ідентифікатором, а не об'єктом: замовлення зберігає
customer_id, а не тримаєCustomer; - узгодженість між агрегатами - кінцева: через доменні події й окремі транзакції.
Конкурентність: два користувачі змінюють одне замовлення - потрібне оптимістичне блокування (версія агрегату) чи блокування рядка кореня.
У Laravel з Eloquent межі агрегату - домовленість, а не обмеження мови: моделі позицій усе одно можна змінити напряму. Допомагають код-рев'ю, методи на корені й те, що контролери працюють лише з коренем.