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

Вурдалак

Продвинутый новичок
Я так понимаю, что для него отсутствие возможности что-то заинжектить — это уже anemic. Хотя здесь есть степени градации. Не нужно быть гением, чтобы видеть разницу между
PHP:
class User
{
    public $id;
    public $name;
    public $email;
}
и чем-то вроде https://github.com/yellowflag/cribbb/blob/master/app/Domain/Model/Identity/User.php
Вторая модель явно отражает поведение, которое требуют от модели, она следит за собственными инвариантами, она сообщает с помощью событий о том, какие изменения произошли и т.д.

С точки зрения whirlwind'а и то, и другое — это просто «единица хранения данных», anemic model.
 

Вурдалак

Продвинутый новичок
Хотя в целом я бы мог поменять свой взгляд на это, но мне нужны весомые причины. Сейчас это выглядит противоестественным. К тому же я толком не могу придумать кейса, когда я не могу обойтись без инъекции. Я сам немало раз думал на тему. Entities такие enities.
 

Sufir

Я не волшебник, я только учусь
Sufir, google:// «ddd readmodel».
Вот что накопал: https://github.com/VaughnVernon/IDDD_Samples/tree/master/iddd_collaboration/src/main/java/com/saasovation/collaboration/application/forum и меня это несколько смущает. Получается, как-раз не только непосредственное использование инфраструктурного уровня в слое приложения, но даже и непосредственно SQL. Впрочем, если аккуратно обернуть в отдельный объект исключительно под это заточенный (что бы не было соблазна использовать как-то ещё, например получать и сохранять через него данные и т.п.), видимо ничего страшного и DDD не противоречит.

P.S.: Репозитории у него исключительно get/save.
P.P.S.: Так или иначе всем спасибо за данную беседу, вынес для себя много нового, интересного и в будущем, вероятно, полезного.
 
Последнее редактирование:

Вурдалак

Продвинутый новичок
Sufir, чтение не имеет (не должно иметь) side effects, поэтому оно не так важно. Ты можешь вообще, подписавшись на события модели, генерировать, допустим, статический файл, который будет отдаваться nginx'ом — тут даже до PHP не будет дело доходить.
 

whirlwind

TDD infected, paranoid
whirlwind, конкретно про это http://thinkbeforecoding.com/post/2009/03/04/How-not-to-inject-services-in-entities что скажешь? Об anemic models там речи не идет ведь.
Может ты помнишь, давным-давно у меня была свой фреймворк. Так вот, там в ORM я инжектил в entity интерфейс repo конкретного типа. Это был единственный способ связать entity с остальной системой при этому не увеличивая связанность. Прошло много лет и я до сих пор использую такой подход и считаю его правильным. Единственная проблема в данном случае это циклическая ссылка, которая в некоторых языках может создать проблемы.

Насчет того, что entity не должна иметь атрибуты не являющиеся частью его state это вопрос "чистоты расы". Объясните тогда для чего в Java существует transient? Может его разрабатывали дураки? Мне так не кажется. Большинство вопросов отпадает когда начинаешь покрывать тестами. Статика для тестов всегда зло. Инжект добро. Чем хороша активная модель? Тем что ее легко изменять. Менять легко только через полиморфизм. Это значит никаких публичных атрибутов. Любой текст который настаивает на том, что геттеры и сеттеры это плохо у меня вызывает только желание закрыть страницу. А попытки притянуть за уши сюда inconsistent state только смех. Inconsistent state в мультипоточных решается методами синхронизации:
Код:
void Security::OnQuoteUpdate(const string& symbol,
   const DecimalPtr& bid, uint32_t bidSize,
   const DecimalPtr& ask, uint32_t askSize,
   const DateTimePtr& time)
{
   if (symbol == m_symbol) {
      {
         Lock x(m_cs);
         m_updateTime = time;
         m_bidPrice = bid;
         m_bidSize = bidSize;
         m_askPrice = ask;
         m_askSize = askSize;
      }
      m_onChanged->FireEvent(make_shared<SecurityEvent>(m_onChanged, shared_from_this()));
   }
}
вот так вот в акцессорах

Код:
DecimalPtr Security::GetBidPrice() {
   Lock x(m_cs);
   return m_bidPrice;
}
и с таким интерфейсом

Код:
class Security : public ICriticalSection {
   virtual void Enter() throw() override;
   virtual void Leave() throw() override;
   ...
}
на пропертях так хрен получится (конечно, если volatile или мониторы на уровне объектов не поддерживаются). В php inconsistent state может быть только по причине абсолютной рукожопости или намеренной диверсии. Например, при работе с внешними хранилищами данных. Но никак не с инстансами классов.

Double Dispatch плохо, потому что в интерфейс вылазит такая вещь, которая конечному потребителю нафик не нужна. При этом, связанность таки повышается, ведь entity все равно знает про новый интерфейс. Говоря о примере, это определенно должен быть Send(), который фигачит в $this->repo->Send($this). Так же как и save и delete и аналогичное. Это дает отличный интерфейс entity, активный и богатый на действия прямо здесь и сейчас. Интерфейс, который можно и мокнуть и стабнуть, в отличии от пропертей, который позволяют тестировать только state. Вместо $this->something->anything->another->different->callSomething() или $this->something->callSomething($anything, $another, $different) мы используем $this->something->callSomething(). Для меня разница очевидна и что выбирать понятно. Все до боли просто: если тест у тебя пухнет от моков разных классов, с дизайном беда.

PS. Кстати, в этом примере про IMailService внутри Message это вообще кривизна конкретная. Любая зависимость внутри entity косвено делает зависимыми потребителей. То есть, добавив понятие IMailService в entity мы делаем этот сервис известным всем потребителям (я тож об этом упоминал). Вместо того, что бы выделить сервис отдельно и передавать ему entity, снижая связанность системы. Это как раз то о чем я говорю - если у вас проблемы, значит вы неправильно отразили предметную область на классы. Проблемы в дизайне, а не подходе.
 
Последнее редактирование:

Sufir

Я не волшебник, я только учусь
Sufir, чтение не имеет (не должно иметь) side effects, поэтому оно не так важно. Ты можешь вообще, подписавшись на события модели, генерировать, допустим, статический файл, который будет отдаваться nginx'ом — тут даже до PHP не будет дело доходить.
C side effects то понятно. Смущает меня вот какой момент. Во-первых SQL в application. Допустим мы пользовались MySQL, в разбросанных там и сям запросах использовалась какая-то специфичная фишка, переходим на Postgre и лазим везде проверяем, выпиливаем. Пусть это очень редкая ситуация, которая может и вовсе не произойдет. Но так же этот подход, ну, расслабляет что-ли. Т.е. ты аккуратно разрабатываешь домен, разделяешь слои и т.д. и используешь такие вещи только так как положено. Приходит другой разработчик, ему нужно быстренько допилить какую-то мелкую фичу и он видя куски SQL и не вдаваясь долго лепит какой-нибудь UPDATE рядом. Плюс теряется SPOT. Нужно завтра поменять везде вывод названия (допустим выводить дополнительную информацию в скобках) и опять же ищи все запросы.
Но в принципе, это всё конечно можно решить, если какие-то DTO ввести и запросы прибрать куда-то подальше от application. Ну, можно уже тут придумать, главное я понял.
 

whirlwind

TDD infected, paranoid
Приходит другой разработчик, ему нужно быстренько допилить какую-то мелкую фичу и он видя куски SQL и не вдаваясь долго лепит какой-нибудь UPDATE рядом.
Не давай ему лепить. Используй в таких местах ограниченный интерфейс или ограниченную реализацию. Первое лучше, но дольше. Второе используется сплошь и рядом. Например read-only collections или synchronized collections. Это просто декоратор, который ругается эксепшенами на попытки update. Но при таком подходе косяк обнаружится только в интеграционных тестах или на продакшене.
 

Вурдалак

Продвинутый новичок
Объясните тогда для чего в Java существует transient? Может его разрабатывали дураки? Мне так не кажется.
О, в ход пошли мнимая логическая связь и апелляция к авторитетам. :)

который фигачит в $this->repo->Send($this)
А кофе repository варить не умеет? Называй уж тогда честно — userServiceLocator.
К тому же инъекция всей сущности в Mailer выглядит опасно: на уровне инфраструктуры сущность могут модифицировать, что выйдет за рамки домена.

Обойти зависимость от Mailer'а можно как-то так, кстати:
PHP:
final class User
{
    public function sendEmail($message)
    {
        $this->recordThat(new EmailWasSent($this->email, ....));
    }
}

// ...

$this->userRepository->save($user);
$this->eventDispatcher->dispatch($user->releaseEvents());
Мне вот доставляет неудобство та мысль, что и double dispatch, и зависимость «внутри» entity будут провоцировать на мгновенное выполнение действия (отправка почты, SMS, etc.), хотя это имеет смысл лишь если транзакция завершилась успешно: допустим, мы хотим в модели иметь дату последней отправки SMS: если SMS отправили, а по какой-то причине $user не сохранили, то будет проблема. Обратный же случай, когда изменили $user, но SMS не отправилась, не так страшен: где-то на уровне инфраструктуры скорее всего будет ошибка, что «SMS gateway is down» или что-то в этом духе.
 
Последнее редактирование:

whirlwind

TDD infected, paranoid
>О, в ход пошли мнимая логическая связь и апелляция к авторитетам. :)

А что делать? Java плохо приспособлен под разработку бизнес-приложений? Или вынос этой связи на уровень языка в виде ключевого слова недостаточно убедительно для тебя? Мое мнение не авторитетно, других разрабов тоже. Ну ок, твоя взяла :D

>А кофе repository варить не умеет?

А кто сказал, что он варит? Это способ сделать интерфейс удобным. Что бы писать в императивном стиле на уровне объектов, а не операторов. Есть такой термин делегирование. Не слыхал?

>К тому же инъекция всей сущности в Mailer выглядит опасно: на уровне инфраструктуры сущность могут модифицировать, что выйдет за рамки домена.

Можно по-русски повторить, что ты пытался сказать? Я не понял какая иньекция сущности и о чем вы вообще с автором того поста пытаетесь сказать? Домену нафик не нужен нищебродский сервис типа send(string, string, string). А подобное message без доступа к атрибутам это вообще сферический конь. Тот mailer сервис низкого уровня типа I/O потоков. А домену нужно обслуживание доменных объектов. Хороший доменный мейлер будет содержать методы для отправки сообщений конкретных типов доменным сущностям, а не строкам каким-то.

> Обойти зависимость от Mailer'а можно как-то так, кстати:

Очередная попытка засунуть невпихуемое не туда куда надо. У тебя есть очевидный сводный корень user message. Но нет, мы будем пытаться всему миру показать все подсистемы, как только кто то поинтересуется сущностью user. Похоже не связывается message с user напрямую. Оба могут прожить друг без друга. Вот так надо
Код:
$userMessage->send($user, $message);
И меняем и юзера, и масягу отдельно (в прямом и переносных смыслах, то есть данные и интерфейс). Тот кто инициирует отправку останется рабочим. И никакого нарушения границ контекста нет и ждать никого не обязательно.
 

WMix

герр M:)ller
Партнер клуба
а че $userMessage еще не содержит $user и $message?
 

Вурдалак

Продвинутый новичок
А что делать? Java плохо приспособлен под разработку бизнес-приложений? Или вынос этой связи на уровень языка в виде ключевого слова недостаточно убедительно для тебя? Мое мнение не авторитетно, других разрабов тоже.
Унылая софистика. «Инъекции в entity правильно, потому что в Java есть ключевое слово "transient", а ещё на Java написано больше enterprise'а, а ещё я зареган на phpclub раньше тебя, поэтому я прав».

А кто сказал, что он варит? Это способ сделать интерфейс удобным. Что бы писать в императивном стиле на уровне объектов, а не операторов. Есть такой термин делегирование. Не слыхал?
Бгг, так и service locator напрямую ничего не делает, он делегирует конкретным сервисам. Я и говорю — называй это честно. Это не repository. Repository — имитация коллекции, про кофе этому объекту знать не нужно. Наличие в интерфейсе коллекции метода send() уже попахивает.

Очередная попытка засунуть невпихуемое не туда куда надо.
Да ты опять о своём, я говорю про другое: зависимость всегда можно заменить на domain event (по крайней мере, я пока не вижу обратного примера). Более того, прямой вызов сервиса имеет озвученные выше проблемы, когда действие выполняется раньше фактической фиксации изменений сущности.
 

fixxxer

К.О.
Партнер клуба
Разве это ответственность репозитория? Всегда считал, что репозиторий - это "доставалка" и "сохранялка", не более.

repository |riˈpäzəˌtôrē| noun (pl. repositories)
a place, building, or receptacle where things are or may be stored: a deep repository for nuclear waste.
• a place in which something, esp. a natural resource, has accumulated or where it is found in significant quantities: accessible repositories of water.
• a person or thing regarded as a store of information or in which something abstract is held to exist or be found: his mind was a rich repository of the past.
ORIGIN late 15th cent.: from Old French repositoire or Latin repositorium, from reposit- ‘placed back,’ from the verb reponere (see repose2) .
Это значит никаких публичных атрибутов. Любой текст который настаивает на том, что геттеры и сеттеры это плохо у меня вызывает только желание закрыть страницу.
Я говорил, что геттеры и сеттеры - плохо, не в смысле, что публичные атрибуты лучше (лол), а в смысле, что они говорят внешнему миру о наличии у модели определенных атрибутов, когда без этого знания можно было бы обойтись вообще. Tell Don't Ask principle. И в этом смысле откуда этот твой Swiss Army Knife Repository знает, куда послать емейл? $user->getEmail()? А зачем тогда вообще send() у entity? Ой, у тебя вообще в entity есть все вызовы репозитория? Так их же мокать все время придется, или как?

в этом примере про IMailService внутри Message это вообще кривизна конкретная
Мне тоже не нравится, я потому этот разговор и начал, что мне все варианты не нравятся. С евентами, как у Вурдалака, вариант, но это частный случай, не всегда подходит.

Может ты помнишь, давным-давно у меня была свой фреймворк
Помню, потому в частности и интересно твое мнение. Но тебя сложно понять, т.к. у тебя общепринятые термины имеют какое-то свое значение. Это бывает, и у меня тоже постоянно, но я с собой борюсь. Сейчас твоя архитектура более-менее понятна (хотя выглядит странной), но тут явно надо вводить другие термины.
 

Вурдалак

Продвинутый новичок
Фактически, «активная модель» whirlwind'а — это обобщение active record. Запихал репозиторий в $user и делаешь $user->save(). А чё.
 

fixxxer

К.О.
Партнер клуба
Приведи пример, интересно.
С емейлом могу только высосать из пальца с очевидным решением, которое обойдет обсуждаемый вопрос. Но все же пусть так - предположим, что надо проверить существование mx-записи, и если она вдруг пропала, выбросить исключение, ничего не сохраняя.

Фактически, «активная модель» whirlwind'а — это обобщение active record. Запихал репозиторий в $user и делаешь $user->save(). А чё.
Вот та же мысль была, кстати, но не стал писать, вдруг я чего не понял.
 

whirlwind

TDD infected, paranoid
Бгг, так и service locator напрямую ничего не делает, он делегирует конкретным сервисам. Я и говорю — называй это честно. Это не repository. Repository — имитация коллекции, про кофе этому объекту знать не нужно. Наличие в интерфейсе коллекции метода send() уже попахивает.
Во-первых, это не я предложил связывать юзера с отправкой мыла таким образом. Это чье-то рацпредложение в топике было. Я так сразу и сказал, что это две самостоятельные сущности и связывать их односторонней связью не получится (ну и следовательно в репо это не будет). В репо обычно какие то агрегатные функции или типо того попадает. Зависит от типа объекта. Во-вторых, я не уверен, что service locator работает как ты описываешь. По моему, это просто точка доступа к сервисам. IoС контейнер таже самая хрень фактически. Кому он там делегирует? В третьих, если тебя смущает активная логика, то отмотай топик назад и вспомни, что я прям так сразу и говорил - сервисный уровень. Не тупо единица хранения, не тупо коллекция, а активно-напичканные логикой интерфейсы, которые освобождают потребителя от связанности с охулиардом подсистем для совершения простого действия. Что бы решать задачу здесь и сейчас. А если что-то изменится, то интерфейс прозрачно избавит потребителя от влияния этих изменений. Смысла повторять это еще пару страниц что бы до тебя дошло я не вижу.

PS. Кстати, насчет подобных репо. Это дает очень удобный эффект для расширения динамических свойств entity. Например, для ордера можно узнать является ли он по сумме больше среднего. Как это делается? Запрос к репо и сравнение собственной суммой. Таких примеров масса. Функция простая. И городить ради а+б какую-то хреномуть реально не стоит.
 
Последнее редактирование:

whirlwind

TDD infected, paranoid
И в этом смысле откуда этот твой Swiss Army Knife Repository знает, куда послать емейл? $user->getEmail()? А зачем тогда вообще send() у entity? Ой, у тебя вообще в entity есть все
В том-то и дело, что при правильном дизайне у тебя нет необходимости сохранять объект. Такая необходимость возникает, если пихать всякие адреса, комнаты, классы, сообщение и прочее, что не является частью entity в соответствии с предметной областью. Новые связи представляют свои entities. C ними и ведется работа в контексте. Мне кажется это настолько очевидным, что мне очень трудно понять в чем у вас затык. Сколько раз в жизни вы меняли свое имя? Скорее всего нисколько. Значит это атрибут персоны. Можете вы прожить без паспорта или может быть хотя бы теоретически связь many-to-many. Все - досвидос эта сущность, здравствуй новая.
 

fixxxer

К.О.
Партнер клуба
Кстати, насчет подобных репо. Это дает очень удобный эффект для расширения динамических свойств entity. Например, для ордера можно узнать является ли он по сумме больше среднего. Как это делается? Запрос к репо и сравнение собственной суммой.
Вот теперь отчетливо вижу RoR-style ActiveRecord :)
 

fixxxer

К.О.
Партнер клуба
В том-то и дело, что при правильном дизайне у тебя нет необходимости сохранять объект. Такая необходимость возникает, если пихать всякие адреса, комнаты, классы, сообщение и прочее, что не является частью entity в соответствии с предметной областью. Новые связи представляют свои entities. C ними и ведется работа в контексте. Мне кажется это настолько очевидным, что мне очень трудно понять в чем у вас затык. Сколько раз в жизни вы меняли свое имя? Скорее всего нисколько. Значит это атрибут персоны. Можете вы прожить без паспорта или может быть хотя бы теоретически связь many-to-many. Все - досвидос эта сущность, здравствуй новая.
Ты на какой-то другой вопрос ответил :) Мне кажется, ты что-то додумываешь за меня, и делаешь свои предположения, по факту уже не имеющие ничего общего с изначальным вопросом.
 
Сверху