CQRS (Command Query Responsibility Segregation) - розділення моделі на дві: одна для змін (команди), інша для читання (запити).
Чому одна модель часто незручна для обох задач:
- запис потребує агрегатів з поведінкою й інваріантами, нормалізованих даних, транзакцій;
- читання потребує денормалізованих, плоских даних під конкретний екран: «список замовлень з ім'ям клієнта, кількістю позицій, статусом доставки й останнім коментарем».
Одна модель на обидві задачі або обростає зв'язками й N+1 для читання, або втрачає виразність для запису.
Рівні CQRS - від легкого до радикального:
1. Розділення в коді - найпоширеніше й найкорисніше:
// команди: змінюють стан через агрегати
final class PlaceOrder { public function handle(PlaceOrderData $data): OrderId { /* ... */ } }
// запити: окремі класи, що читають напряму, без моделей домену
final class OrderListQuery
{
public function get(int $customerId): Collection
{
return DB::table('orders')
->join('customers', ...)
->select(['orders.id', 'customers.name', 'orders.status', ...])
->where('orders.customer_id', $customerId)
->get();
}
}
Читання не обмежене формою агрегатів - оптимізований SQL під екран.
2. Окремі моделі читання (read models) - денормалізовані таблиці чи проєкції, що оновлюються подіями: таблиця order_summaries з усім потрібним для списку.
3. Окремі сховища - запис у реляційну базу, читання з Elasticsearch/Meilisearch чи окремої репліки.
Коли CQRS виправданий:
- складний домен з багатою логікою запису й різноманітними поданнями для читання;
- велика різниця навантажень: читань у сотні разів більше, і їх треба масштабувати окремо;
- різні вимоги до даних: пошук, звіти, аналітика;
- разом з event sourcing - там CQRS практично обов'язковий (стан будується з подій).
Коли шкодить: у простих CRUD-застосунках - подвоєння коду без вигоди. Фаулер прямо попереджає: для більшості систем CQRS додає ризику й складності.
Ціна окремих моделей читання:
- кінцева узгодженість: користувач зберіг зміну, а список ще показує старе. Інтерфейс має це враховувати;
- синхронізація: проєкції, що не оновилися через помилку, треба вміти перебудувати;
- більше рухомих частин.
Практична порада: почати з рівня 1 (окремі класи-запити) - він дає більшість користі майже без ціни.