Как назвать метод?

whirlwind

TDD infected, paranoid
Вурдалак, Ты издеваешься что ли? Почему ты цепляешься к самым малозначимым и неудачным словам в моем тексте и не видишь главного? Ты говоришь нарушаются границы контекста аффектит транзы и все такое? Да потому что иначе не получится при высокосвязанной системе. Если у вас что-то нарушается, значит вы некорректно отразили предметную область на классы.

Все зависит это от того, как вы реализуете связи. Атрибуты - это плохой способ связывания. Он рабочий, но плохой. Дополнительная карта связей (новая entity), то бишь aggregate root - это хороший способ. Aggregate root это коллекция of references с точки зрения структуры данных (я говорил - пример с many-to-many) и сервис обслуживающий эти связи с точки зрения бизнес-логики. Потому что он решает задачу изолированно от сущностей. При таком подходе легче вносить изменения без влияния на другие подсистемы, которые задействуют в своей работе те-же самые сущности.
 
  • Like
Реакции: AmdY

Вурдалак

Продвинутый новичок
А я сейчас про не про связи, мне не нравится двусторонняя связь тоже, а про то, что при «aggreagate root — сервис» всё разваливается.
 

Вурдалак

Продвинутый новичок

whirlwind

TDD infected, paranoid
Какие вопросы?

> А теперь вопрос: откуда у твоего «сводного корня» берутся сервисы (зависимости), если это лишь сущность?
> Как так получается, что ты собираешься делать save какой-то одной части aggregate? Ты либо весь целиком его сохраняешь, либо нет, в противном случае ты можешь получить inconsistent state.

это один и тот же вопрос. вопрос, когда ты начнеш читать что я тебе пишу.
 

Redjik

Джедай-мастер
я только не понял - это good practice или нет... но я вот только что сам до этого же допер.

Короче при работе с DDD, надо придерживаться определенных правил :D

1) Бить ногами за new и любое создание entity (тут меня сильно Лео сбил с толку своими create() в тестах и вообще), доступ к entity ТОЛЬКО через репо
— это решает проблему синхронизации ... ибо мы с репо вытаскиваем aggregate root (тот же Grade мы вытаскиваем из репо и сохранить мы в репо можем только grade - у нас как такого доступа к Pupil нету, только через Grade)

2) Юзать сервисные слои для валидации на уровне Репо, например constraint, как в ссылке (то есть усли бы у нас была хитрая валидация на на Grade::addPupil, с проверкой - есть там такой Pupil или нет, то можно просто убрать addPupil из публичных методов и добавлять через сервисный слой, скрыв реализацию)
 

Sufir

Я не волшебник, я только учусь
доступ к entity ТОЛЬКО через репо ... ибо мы с репо вытаскиваем aggregate root
Очевидно так. Создание сущности через new как и явный вызов Repository::save(), по логике, допустим в случае создания новой сущности. Например формируем новый "класс". Сущности ещё не существует вообще, в репо в том числе (можно обернуть в сервис как здесь https://github.com/yellowflag/cribbb/blob/master/app/Domain/Services/Groups/NewGroupService.php). Соответственно мы создаем новый "класс", сохраняем в репо, а дальше уже создание через new противоречит непрерывности жизненного цикла.

усли бы у нас была хитрая валидация на на Grade::addPupil, с проверкой - есть там такой Pupil или нет, то можно просто убрать addPupil из публичных методов и добавлять через сервисный слой, скрыв реализацию)
А вот это мне кажется странным. Т.е. я выше предлагал, как вариант, ввести службу для перевода "ученика" в другой "класс" (там задействовано две сущности классов и одна ученика, и однозначно не понятно которая из сущностей может/должна за это отвечать), с другой стороны всё подряд начинать вытаскивать из сущности и плодить сервисы мне кажется неверным. Или не о том?

У меня ещё вопросы чисто практические остаются... Например инициализации объектов в репозитории. Допустим некий билдер собирает объекты из данных полученных от DataSource. Как, к примеру, объекту $grade присвоить коллекцию "учеников", а так же "классного руководителя"? По идее, простые публичные сеттеры как таковые противоречат идее.
Т.е. $grade->addPupil() и $grade->setFormMaster() - должны быть не просто сеттерами, а их вызов приводит к вызову "хитрой валидации" и целому ряду действий, а в том числе и сохранению изменений в репе. При воссоздании объекта из репозитория, очевидно это всё не нужно (что-то просто излишне, а что-то и недопустимо). Как тут быть, не через Reflection же собирать объект?

P.S.: Вообще, как только у меня более менее начинает складываться картина, так через десять минут размышлений всё становится ещё менее понятно, чем было и возникает ещё больше всяких вопросов.
 
Последнее редактирование:

whirlwind

TDD infected, paranoid
За new надо бить ногами в любом случае, кроме value object. Для этого существуют фабрики. Когда покрываешь модульными тестами, об этом знаешь. Что бы избежать большого количества фабрик, приходится выносить логику в сервисный слой и мокать его в тестах. Тогда вместо этого
Код:
public function create(User $user, $name)
{
    $this->checkNameIsUnique($name);
получаем тестируемое это

Код:
public function create(User $user, $name)
{
  $this->service->checkNameIsUnique($name);
при таком раскладе create едет в репо. Если связь такая однозначная, то в репо группы. Если нет, то в репо сводного корня. Поскольку логика и так лежит в другом месте, то нет никакого смысла для отдельного класса сервиса. С точки зрения потребителя репо и есть сервис.
 

Redjik

Джедай-мастер
P.S.: Вообще, как только у меня более менее начинает складываться картина, так через десять минут размышлений всё становится ещё менее понятно, чем было и возникает ещё больше всяких вопросов.
+100500 =)))

У меня ещё вопросы чисто практические остаются... Например инициализации объектов в репозитории. Допустим некий билдер собирает объекты из данных полученных от DataSource. Как, к примеру, объекту $grade присвоить коллекцию "учеников", а так же "классного руководителя"? По идее, простые публичные сеттеры как таковые противоречат идее.
Т.е. $grade->addPupil() и $grade->setFormMaster() - должны быть не просто сеттерами, а их вызов приводит к вызову "хитрой валидации" и целому ряду действий, а в том числе и сохранению изменений в репе. При воссоздании объекта из репозитория, очевидно это всё не нужно (что-то просто излишне, а что-то и недопустимо). Как тут быть, не через Reflection же собирать объект?
Это то как раз самая легкая часть, в любом ЯП есть достаточно хаков, чтобы все работало по DDD, слой инфраструктуры как раз вовсю использует эти хаки.
То есть на уровне домена, мы разрабатываем бизнес логику.
На уровне инфраструктуры хакаем protected и private (в случае PHP через рефлексию - все верно - доктрина именно так и делает).
Так же довольно много размышлений в интернетах по поводу lazy load и вытаскивания коллекций - самый вмениямый вариант через прокси, что опять же доктрина умеет из коробки.

И наконец урвень приложения - он уже не использует никакие хаки и работает с тем, что подсунула ему инфраструктура по правилам бизнес логики описанной в домене.
 

Redjik

Джедай-мастер
при таком раскладе create едет в репо. Если связь такая однозначная, то в репо группы. Если нет, то в репо сводного корня. Поскольку логика и так лежит в другом месте, то нет никакого смысла для отдельного класса сервиса. С точки зрения потребителя репо и есть сервис.
мне не кажется, что создание Entity должно происходить в репо
 

Sufir

Я не волшебник, я только учусь
мне не кажется, что создание Entity должно происходить в репо
Я тоже так думаю. Как по мне, так ответственность репо вообще сводится к двум задачам - get и save.

Ну, удаление из репо это лишь одна из возможных операций выполняемых в момент прекращения жизненного цикла сущности. Это же не просто удаление записи из БД.

На уровне инфраструктуры хакаем protected и private (в случае PHP через рефлексию - все верно - доктрина именно так и делает).
Так же довольно много размышлений в интернетах по поводу lazy load и вытаскивания коллекций - самый вмениямый вариант через прокси, что опять же доктрина умеет из коробки.
Так, хорошо, пусть будет рефлексия. Тогда ещё вопрос.
По-поводу формирования UI. Пример из той же предметной области... Нужно вывести таблицу со списком всех учеников, с пагинацией и возможностью фильтрации. В таблице выводится ФИО, адрес и т.п., т.е. к примеру сотня сущностей, с возможно целым рядом подсущностей и ValueObject'ов (разумеется загружаем исключительно те, что используются). Это уже несколько сотен объектов.

Теперь форма-фильтр, в ней присутсвуют поля для фильтрации по датам/времени, поиск по имени и пр., а в том числе 8-10 селектов, для выборки "учеников" по "классам", изучаемым "предметам", "преподавателям", "факультативам" и ещё что-нибудь, что там может быть. Каждый элемент в системе является "сущностью" (и существуют бизнес правила, в которых они могут быть сводными корнями, ну или просто подсущностями других сущностей), ну, некоторые может быть, ValueObject. Их может быть много, от пары десятков, до трёх-пяти сотен на каждый селект.

Отсюда закономерный вопрос, как это реализовать правильно с точки зрения DDD? Неужели я должен задействовать всю цепочку от репозитория, с билдерами, рефлексией и т.д. и создавать в памяти несколько сотен объектов (только для UI, а ещё сами сущности информацию о которых мы выводим), когда мне достаточно простейшего SELECT id, title FROM... (обёрнутого в какой-нибудь QueryObject или что-то ещё), но UI ведь не должен обращаться к слою инфраструктуры.
 

whirlwind

TDD infected, paranoid
Если модель анемичная, то нет никакой проблемы с подходом когда репо только get/save. Для активной (богатой) модели нужно много инжектить. Это делает 1) неудобным инстанцирование где-то там, 2) сложно вносить изменения в модель, потому что ее инстанцирование размазано по системе (типичная проблема new). Куда проще добавить фабричные методы в репо. Тогда при изменении внутренностей клиенты останутся работоспособными. Для новых клиентов, требующих расширенные функции, добавляются новые фабричные методы.
 

Вурдалак

Продвинутый новичок

whirlwind

TDD infected, paranoid
Я пишу со 100% покрытием тестами. Количество рефакторингов в моих проектах огромно. Но в конце каждой итерации мне нужен рабочий продукт. Если я не смогу сделать достаточно гибкий дизайн, я просто не успею сделать релиз за неделю. Либо я не успею покрыть его тестами, потратив время на переписывание анемичной модели, вместо расширения интерфейсов. Если ты считаешь что мой подход заблуждение, а твой нет, то неплохо было бы выложить что нибудь посерьезнее пары постов от не вполне понятных личностей. Я сразу понял что твой подход это анемичная модель, когда указал тебе на тот факт, что ты определил роль entity как единица хранения данных. Можешь мотнуть топик и вспомнить. Только ни одного аргумента или хорошего примера в пользу такого подхода я не увидел, к сожалению. По ссылкам есть здравое зерно и known issues, тут я спорить не буду.
 
Последнее редактирование:
Сверху