Symfony DDD сущности, репозитории и доктрина

MiksIr

miksir@home:~$
Если чем-то не нравится рефлексия
Да в общем нравится. Особо если ее еще закешировать, вообще круто.

Выглядит это все, конечно, страшно, вот эти две сущности. Я бы понял еще, если бы у нас AR был, но с датамапером оверхед какой-то с сущностями. И получается, только потому, что датамапер "не айс".
 

Вурдалак

Продвинутый новичок
Можно без Doctrine и двух сущностей: SELECT + рефлексия для получения объекта сущности. В сущности все изменения приводить к событиям и выполнять SQL-запросы на полученные изменения (getRecordedEvents):
PHP:
final class UserProjector
{
    public function projectUserWasRegistered(UserWasRegistered $event)
    {
        // INSERT INTO users
        // SET `id` = $event->getUserId(),
        //     `email` = $event->getEmail(),
        //     ...
    }

    public function projectUserWasBanned(UserWasBanned $event)
    {
        // UPDATE users
        // SET banned = 1
        // WHERE
        //     `id` = $event->getUserId()
    }
}
(см. http://verraes.net/2014/03/practical-event-sourcing/; там речь про ES, но ничего не мешает так делать и без ES). При нескольких событиях все запросы на изменение будут в одной транзакции.

Если же требуется модель чисто на чтение, то пишешь query service, который делает SQL-запрос в users напрямую, тут можно добавить кеширование и прочее.

С одной стороны, примитивный подход, но достаточно прозрачный.
 

MiksIr

miksir@home:~$
Угу, один из путей, которые сейчас рассматриваю - получится, в общем, самописный датамапер. Вот только, боюсь, что по ходу дела хотелки всякие сделают из него монстра.
Второй путь - все же использовать domain entity с доктриной, как https://github.com/dddinphp/last-wishes тут, например.
Все же не хочется сейчас слишком закапываться в инфраструктурную часть, может и оставлю доктриновкую сущность, будет на перспективу для логики, связанной с базой... не знаю, например, массив в строку, там, конвертнуть ;)
 

Вурдалак

Продвинутый новичок
Мне кажется, что с Doctrine нужно сначала пробовать делать маппинг сразу на domain model. Если не получается, то нужно для конкретной сущности делать кастомный DM.

Там всё равно все корневые сущности в DDD между собой не имеют прямой связи, поэтому нет необходимости париться, чтобы инфраструктура всех репозиториев была одинакова.
 

MiksIr

miksir@home:~$
К слову, VO используются как PK. Через поддержку своих типов в доктрине.
Наверное, так же и массив VO можно, через тип - сериализатор.
 

MiksIr

miksir@home:~$
Вот с прямым мапингом на доменную модель пока две проблемы.

Во-первых - коллекции. Доктрина хочет или array или свою коллекцию. Почему она не может работать с любой другой коллекцией, реализующей интерфейс ArrayAccess мне понять сложно ;) Пришлось доменный ArrayCollection реализовать от доктриновского интерфейса. Кстати, в trip planner оказалось сделано так же.

Вторая проблема - single table inheritance. Что бы оно работало - доктрине нужно наследование сущностей от одной общей сущности. Т.е. приходится вводить в домен нечто типа abstract class Attribute только для того, что бы работала доктрина. Ну или не сильно страшный костыль?
Но это фигня, а вот как подтянуть различные кастомные типы для первичного ключа в зависимости от discriminatorMap - не понятно вообще
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Во-первых - коллекции. Доктрина хочет или array или свою коллекцию. Почему она не может работать с любой другой коллекцией, реализующей интерфейс ArrayAccess мне понять сложно
Мне кажется, это цена за магию с lazy loading.

Пришлось доменный ArrayCollection реализовать от доктриновского интерфейса.
Не уверен, что кастомные методы коллекции будут работать после получения сущности из БД.

Но это фигня, а вот как подтянуть различные кастомные типы для первичного ключа в зависимости от discriminatorMap - не понятно вообще
Я не понял это предложение. Если ты решил маппить напрямую, то это делает за тебя Doctrine, нет?
 

MiksIr

miksir@home:~$
Я не понял это предложение. Если ты решил маппить напрямую, то это делает за тебя Doctrine, нет?
Если я делаю single page inheritance, то и тип первичного ключа определяю в базовом классе... просто не могу его не определить там - доктрина ругается. Ну ставим там тип AttributeId, например. А мне нужно в таблицах "наследниках" свои VO, условно MaterialId и ColorId т.п....
 

MiksIr

miksir@home:~$
Есть характеристики, которые объедены в группы. Т.е. такой EAV только без V. Хотел на уровне домена каждую группу характеристик представить своей моделью, а в базе положить их в одну таблицу с discriminator по имени группы.
 

MiksIr

miksir@home:~$
В общем еще один глупый вопрос. По работе с файлами/картинками.

Есть сущность "объявления", у нее есть фото. Вижу два пути.

1) Файл (путь к нему или бинарные данные) в каком-то виде (VO, например) уходит в сервис добавления файла в домене. Сервис создает сущность "фото", репозиторий сохраняет метаданные в базу, файл на диск. Он же собирает сущность обратно с подстановкой урла (конечно, посредством сервиса сохранения на диск). "Объявление" тут вообще не причем, привязка фото к объявлению произойдет при сохранении объявления.
Тут вопрос - где делать, например, ресайз. В слое приложения, в доменном сервисе или вообще в репозитории...

2) Сервис сохранения файла и его получения (получение урла) в слое приложения (или в persistence слое, но вызывается из слоя приложения). Объявление - сводный корень для фото. Доменная модель фото содержит только метадату, которая и сохраняется в базу при сохранении объявления. Т.е. в приложении при получении объявления с фото мы проходимся по фото, что бы получить из сервиса их урлы.
 

WMix

герр M:)ller
Партнер клуба
создать ресайзер или манипулятор как мысль пойдет который принимает файл обрабатывает и возвращает файл.
у меня файл типа обьект который может стримится типа с хидером, сохранятся (перемещаться из темпа), возвращаться бинером или метой
 

fixxxer

К.О.
Партнер клуба
А чем по простому не устраивает?

PHP:
$photo = $photoFactory->createFromUploadedFile($uploadedFile);
$posting->addPhoto($photo);
$storage->put($photo, $uploadedFile);
try {
    persist($posting);
} catch {
    $storage->delete($photo);
    throw;
}
...
$url = $photo->getPublicUrl($photoUrlBuilder[, $size]); // double dispatch, где-то там в presentation
 
Последнее редактирование:

MiksIr

miksir@home:~$
создать ресайзер или манипулятор как мысль пойдет который принимает файл обрабатывает и возвращает файл.
у меня файл типа обьект который может стримится типа с хидером, сохранятся (перемещаться из темпа), возвращаться бинером или метой
Если я правильно перевел, то что-то такое и хотел сделать. В частности хотелось, если, например, файл пришел в base64, а фото кладем в базу или отправляем куда-то по webdav - вообще исключить запись в файл. И наоборот - если файл на диске и сторадж на этом диске - что бы делалось copy просто.
Тут тогда вопрос - этот ресайзер где, в каком слое ;)
 

Вурдалак

Продвинутый новичок
Я бы не стал в domain делать какие-то операции, которые напрямую затрагивают файлы. Возможно, я приведу не совсем корректную аналогию, но для меня это примерно как ожидать от кода
PHP:
$employee->fire();
что сейчас сотрудника физически выкинут из офиса, сказав, что он уволен.
Модель лишь отражает действия из «реального» мира, но не выполняет их «по-настоящему».
В данном контексте модификация файлов — это «реальное» действие.

Как пример, если всё-таки хочется ресайз отразить в модели, я бы сделал что-то вроде следующего (Photo — aggregate root):
PHP:
// ResizePhotoHandler, application
public function handle(ResizePhoto $command) {
    $photo = $this->photoRepository->findById($command->getPhotoId());
    $photo->resize($command->getHeight(), $command->getWidth()); // throws InvalidPhotoSize exception?
    $this->photoRepository->save($photo); // PhotoWasResized(42, 500, 600)
}
По событию PhotoWasResized я бы где-нибудь в инфраструктуре физически поменял бы размер картинки.
Модель же при этом тут носит скорее информационный характер, что фотки можно ресайзить в принципе.
Помимо этого, вероятно, модель может иметь ограничения на размер фоток (иначе выкидывается exception InvalidPhotoSize).

Но на необходимость как-то отражать ресайз стоит посмотреть и с другой стороны: возможно, это выходит за рамки твоего контекста с объявлениями. С точки зрения интерфейса, конечно, это может быть неочевидно: вот ты пишешь текст объявления, прикрепляешь фотку, тут ещё и отресайзить её захотел... Но по факту понятие «фотка» в первом контексте может быть просто value object'ом (тупо имя/адрес, даже метаданные, возможно, не нужны), а во втором — «фотка» может быть частью достаточно большого контекста, связанного с редактированием фотографии, быть сущностью, иметь свой жизненный цикл и т.д.
И если посмотреть с этой стороны, то можно одному контексту уделить больше внимания (domain modelling), а в другом — CRUD all the things.

Почитай на тему bounded contexts, это одно из ключевых понятий в DDD. Невозможно (и нерационально) везде пытаться внедрять DDD и пытаться всё делить на слои. Некоторые контексты это не заслуживают, задача программиста в том, чтобы это почувствовать и не утонуть в overengineering'е.
 
Последнее редактирование:
Сверху