Как огранизовать работу с ActiveRecord и агрегированными данными в нем?

Absinthe

жожо
Существует модель Measurement.
Требуется получить агрегированные данные. Где должен находиться такой код?
Навряд ли в модели Measurement: этот код не связан ни с каким объектом типа Measurement. К тому же он будет статичным.

С течением времени (обычно очень скоро для центральных классов домена) такой класс разрастается до тысяч строк.
Как уменьшить сложность?
Переносить логику в трейты (и потом искать, где что находится)? Создавать сервисы? Тогда возникает вопрос, зачем вообще был выбран Active Record, а не Data Mapper (возможно с анемичными моделями, тогда проблема роста не будет существовать).
 

Вурдалак

Продвинутый новичок
Как раз недавно обсуждали CQRS: различные агрегированные данные получают через query-сервисы.
PHP:
interface FooMeasurementQueryService
{
    /**
     * @return FooMeasurementDTO
     */
    public function getAggregatedDataForPeriod(Period $period);
}
В реализации ты можешь напрямую делать сложные SQL-запросы.

Модель Measurement будет нужна только в command-сервисах, которые что-то изменяют, либо в примитивных query-сервисах.

С моей точки зрения, конкретно тут сравнение DM vs ActiveRecord ни к месту: методы для получения агрегированных данных в DM тоже будут неуместны. DM нужен для маппинга сущности в хранилище и обратно. Агрегированные данные сущностью не являются, можно считать нарушением SRP.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
AR - это просто одна из форм DM. По большому счету, массив результата fetchAll() - тоже DM.
Я тоже делаю сервисы.

Я бы определил агрегированные данные как отдельную, производную сущность. А что такое SRP?
 

Absinthe

жожо
Я бы определил агрегированные данные как отдельную, производную сущность.
Почему производную?
Делаешь это обычной моделью, которая лежит рядом с AR моделями? Или уровень сервиса?

Один из 5 основных принципов ООП, который нарушается в Active Record.
 

Вурдалак

Продвинутый новичок
https://en.wikipedia.org/wiki/Single_responsibility_principle

Это самая очевидная мысль, но хотелось бы больше сродства с Active Record.
Завтра тебе понадобится вывести отчёт, который джойнит 4 различные таблицы, которые соответствуют различным ActiveRecord-моделям. В какой из классов ты запихнёшь запрос на получение этого отчёта?

И я повторюсь, что при записи тебе обычно нужна транзакционная целостность. При чтении — кеширование. Это взаимоисключающие вещи.

Разве сервисы вписываются в эту концепцию?
Не очень понял вопрос. С точки зрения presentation layer ты всегда работаешь с сервисами.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
@Absinthe, я не знаю какую модель ты считаешь обычной. Сходство AR и модели для аггрегации очень ограничено - у модели аггрегации read-only интерфейс.
Мне удобнее это делать в сервисе именно по причине "отчёт, который джойнит 4 различные таблицы". Вырожденный случай аггрегации по одной таблице - редкость.
 

MiksIr

miksir@home:~$
Завтра тебе понадобится вывести отчёт, который джойнит 4 различные таблицы, которые соответствуют различным ActiveRecord-моделям. В какой из классов ты запихнёшь запрос на получение этого отчёта?
Например, в отдельный, который работает с вьюхой из этих таблиц ;) Если ему уж так сродства хочется.
 

Absinthe

жожо
Не очень понял вопрос. С точки зрения presentation layer ты всегда работаешь с сервисами.
А с точки зрения фреймворков типа Laravel или Rails мы используем объекты AR из контроллера: нет никакого Service Layer, все работают сразу с модельками. И часть инфраструктуры фреймворка на это рассчитана, например получение экземпляров моделей прямо из роутера.
Вопрос в том, стоит ли в подобных случаях использовать Controller => Service => AR, или использовать Controller => {Service, AR}
 

Adelf

Administrator
Команда форума
А с точки зрения фреймворков типа Laravel или Rails мы используем объекты AR из контроллера: нет никакого Service Layer, все работают сразу с модельками.
Не говори плиз за всех.

И часть инфраструктуры фреймворка на это рассчитана, например получение экземпляров моделей прямо из роутера.
Обычно тут имеются ввиду другие модели. ViewModels я их называю. Но многим влом ;-)
 

Вурдалак

Продвинутый новичок
А с точки зрения фреймворков типа Laravel или Rails ...
Фреймворк не должен диктовать архитектуру приложения. Если ты бездумно юзаешь все примеры из мануала, то это проблема не инструмента, а твоя.

Controller => Service => AR, или использовать Controller => {Service, AR}
Завтра тебе понадобится вывести отчёт, который джойнит 4 различные таблицы, которые соответствуют различным ActiveRecord-моделям. В какой из классов ты запихнёшь запрос на получение этого отчёта?
 

fixxxer

К.О.
Партнер клуба
Завтра тебе понадобится вывести отчёт, который джойнит 4 различные таблицы, которые соответствуют различным ActiveRecord-моделям. В какой из классов ты запихнёшь запрос на получение этого отчёта?
Acme\Model\Utils!
 

Absinthe

жожо
Завтра тебе понадобится вывести отчёт, который джойнит 4 различные таблицы, которые соответствуют различным ActiveRecord-моделям. В какой из классов ты запихнёшь запрос на получение этого отчёта?
App\Model\MeasurementReport - отдельная модель для конкретного отчета, лежит рядом с другими AR моделями и использует их, но сама не AR.

Если ты бездумно юзаешь все примеры из мануала, то это проблема не инструмента, а твоя.
Это способ использования фреймворка, который задумывался его разработчиками, и под который разработана вся его инфраструктура.

Фреймворк не должен диктовать архитектуру приложения.
От фреймворка зависит, некоторые диктуют "пользуйтесь как хотите, мы слабосвязанные компоненты", но с некоторыми фреймворками удобно работать только при архитектуре, задуманной их авторами, на что и идет упор (документация, курсы, best practices).
 

Adelf

Administrator
Команда форума
@Absinthe, никто не задумывал Laravel чтобы из контроллеров сразу в ActiveRecord лезли. Он не просто так содержит в себе IoC по умолчанию и чуть ли не настаивает на его использовании. Сделано это, чтобы было удобно писать сервисы, делить код на слои и т.д. Не надо на материалах для новичков делать выводы о какой-то там диктуемой архитектуре.
 

Вурдалак

Продвинутый новичок
@Absinthe сначала ты пишешь говнокод ради скорости, потом «с течением времени» удивляешься этому говнокоду.

А какие вообще у тебя могут возникнуть проблемы с использованием query-сервисов? Ты там че-то приводил пример с роутингом, но это для входных данных, а зачем это нужно для кастомного объекта-отчёта?
 

Вурдалак

Продвинутый новичок
@Absinthe, никто не задумывал Laravel чтобы из контроллеров сразу в ActiveRecord лезли. Он не просто так содержит в себе IoC по умолчанию и чуть ли не настаивает на его использовании. Сделано это, чтобы было удобно писать сервисы, делить код на слои и т.д. Не надо на материалах для новичков делать выводы о какой-то там диктуемой архитектуре.
Кстати, да, там какое-то подобие command bus добавили в 5-й версии? Это как раз интерфейс для command-сервисов.
 

флоппик

promotor fidei
Команда форума
Партнер клуба
Я и писал, когда пятерка вышла, что комманд бас был бесполезен, потому что его никуда внутри фреймворка не был воткнут нормально, и эвенты были параллельно с ними, перекрывая с ним кусками функционал, и просто «был» Там надо было выкинуть что-то одно, и завязаться целиком на второе. По привычке видимо решили оставить евенты, хотя я бы предпочел что бы выкинули евенты и доделали команд бас.
 
Сверху