Здесь Dapper, но подойдёт любой лёгкий инструмент или ORM в режиме проекций. База сама считает заказы и соединяет таблицы, по сети едут девять полей, а в IOrderRepository не появляется ни одного метода ради экрана.
Почему Repository для Query — антипаттерн
На стороне чтения Repository ничего не защищает: агрегат не меняется, инвариантов нет. Он возвращает агрегаты, когда экрану нужна проекция, и прячет за абстракцией то, что чтению как раз нужно: JOIN, GROUP BY, индексы.
Если сделать «репозиторий для чтения», который возвращает DTO, получится тот же query handler с лишним интерфейсом посередине.
Шпаргалка: когда Repository нужен, а когда нет
Чего не делать:
- Писать мимо Repository. Прямой UPDATE обходит инварианты агрегата, и свобода чтения на запись не распространяется.
- Принимать бизнес-решения по query-DTO. Она собрана под экран и не гарантирует целостность, поэтому команда загружает агрегат.
- Тащить read-запросы в репозиторий «для единообразия». Так он и дорастает до 30 методов.
- Заводить отдельное хранилище и события, когда хватает одного SELECT. Read-база с проекциями по событиям — это уже CQRS, он нужен, когда чтение упирается в нагрузку. Экрану профиля хватит запроса к той же базе.
В CRUD-модуле без доменной логики, например в справочнике городов, Repository может быть не нужен и на запись: защищать нечего.
| Задача | Repository нужен? | Чем читать и писать |
|---|
| Изменить состояние агрегата | Да | Command handler: загрузить агрегат через Repository, вызвать метод, сохранить |
| Показать список | Нет | Query handler: SQL-запрос, плоская DTO под экран |
| Отчёт по нескольким агрегатам | Нет | Query handler: один запрос с JOIN и агрегатными функциями к той же базе, отдельная read-модель только при проблемах с нагрузкой |
| Проверить существование для бизнес-правила | Да | Метод Repository или доменный сервис внутри команды, решение принимает домен |
| CRUD-справочник без доменной логики | Нет | Прямые запросы или ORM без агрегатов и Repository |
Для выбора хватит одного вопроса: может ли операция нарушить бизнес-правило? Если может, нужен Repository. Если нет, обойдись без него.
Итог
- Repository в Clean Architecture — коллекция агрегатов для изменения состояния, а не обязательная обёртка над базой.
- Чтение не нарушает бизнес-правил, поэтому query handler идёт в базу напрямую и отдаёт плоскую DTO.
- Запись мимо Repository по-прежнему под запретом.
Открой самый большой репозиторий в своём проекте и отметь методы, которые вызываются только ради ответа фронтенду. Каждый из них — кандидат в query handler.