Делать метод в сущности или нет?
20 августа 2018
Вроде простой вопрос, но не так он прост как кажется. Если ударяться в крайности, с одной стороны у нас будут анемичные модели, где сущность не содержит какой-либо логики, а с другой - модели, которые делают слишком много всего, что с ними напрямую не связано. Как выбрать в конкретном случае, стоит ли оставить метод в сущности или вынести в отдельный класс?
Я для себя вывел несколько критериев оставления метода в сущности. Они, конечно, не покрывают всех случаев, но помогают принять решение.
- Метод работает с экземпляром сущности.
- Метод работает или только с самой сущностью или с небольшим количеством внешних данных, передаваемых через аргументы.
- Метод применим ко всем контекстам, в которых используется сущность.
- Метод описывает поведение самой сущности, а не технические детали, такие, например, как сохранение или загрузка из базы.
Комментарии RSS по email OK
Может быть можно увидеть какие-то небольшие наброски кода ? :)
Еще заметка. Если не ошибаюсь, в руководстве DDD написано что в случае когда непонятно к какой из двух сущностей относится логика, то нужно выносить ее в сервис для работы с обоими сущностями. Так же в самой сущности нужно максимально абстрагироваться от реализаций внутри метода и по мере возможности использовать интерфейсы. Собственно, то, что ждет yii - di.
Всё верно. Только не "нужно", а можно. В архитектуре практически нет 100% верных решений.
Я исхожу из принципа Information Expert, Low Coupling и High Cohesion из GRASP при прнятии таких решений. Если грубо - у кого есть необходимые данные для выполнения операции и кто не станет от этого переполнен зависимостями, тот и получит метод.
Александр, у меня вопрос по п.2. Исходя из него Вы в сущности зависимости не внедряете? Я внедряю. В методах типа init() или конструкторе иногда обращаюсь к сервис-локатору за зависимостями. Спасает от анемии и сервисов на "каждый чих", повышает Cohesion сущности.
И что под сущностью понимается? ActiveRecord в качестве сущности применяете? Я применяю, т.к. изоляция от БД это долго и дорого, а иногда вредно и недостижимо.
Внедряю, но не всегда. Внедряю через конструктор или
init()
если та сущность, в которую внедряем, является по сути агрегатом. Внедряю через параметры метода в исключительных случаях.Под сущностью понимается объект, который можно идентифицировать. Это не обязательно Active Record, но часто да.
ИМХО, но не считаю внедрение зависимостей в сущность хорошим решением.
Сущность не должна иметь какие-либо зависимости и должна отражать только запись в таблице. Иначе, постепенно, сущность превращается (легким движением руки) в службу/фасад, что влечет размывание границ сервисного слоя приложения, тем самым ухудшая расширяемость, сопровождение и разработку в целом.
Когда сущность остается лишь посредником для передачи данных между хранилищем и сервисным слоем - это, прекрасно. А если она начинает делегировать на себя чужие обязанности - тут, увы, но что-то пошло не так.
Если не делать внедрения, сущности почти гарантированно получатся анемичными. Я сам через это прошел, поверьте). Тоже раньше так думал - "максимально облегчить сущности, убрать все зависимости, оперировать только данными самой сущности и т.п.". В дальнейшем, я пришел к выводу, что если совсем избегать внедрения, получится что сервисный слой - это набор процедур (хоть и написанных в виде классов) оперирующих данными без поведения - сущностями. Сущности получаются несамостоятельными, они сами ничего не могут без сервисов, только хранить и менять свои данные. А это возврат к процедурному программированию с его разелением на функции и структуры данных. В ООП объекты должны быть "умными" и содержать поведение. Для устраннения большого количества зависимостей и вызовов в сущностях я использую события. Многие вещи, которые не модифицирую данные сущности, но при этом вызываются из нее можно перенести в обработчики событий. В Yii для обработки событий хорошо подходят поведения. Они могут иметь свои зависимости, их можно тестировать отдельно. Поэтому, кстати, я против идеи убрать поведения из Yii в пользу трейтов. Трейты не могут быть обработчиками событий.
@anton_z,
Что в этом плохого? Каждый выполняет свою задачу. Это и есть распределение ответственности. В этом весь и смысл. По-гуглите DDD, например, DDD Quickly (считаю, что на 100 страниц там достаточно инфы, чтобы осознать и понять для старта).
Правильно думали.
Что от сущности, собственно, и требуется - быть посредником и стоять в стороне.
ООП подход не станет процедурным, если у Вас сущности занимаются своими задачами. Она посредник, она отражает состояние записи в таблице. Это уже не процедурный подход, к слову. Странная закономерность. Объекты не должны быть умными, а они могут быть умными.
При таком подходе нельзя гарантировать, что реакция на событие у сущности будет полезно другой части приложение. Это нельзя предугадать и рассчитать.
Поведения должны были просто расширять возможность модели, а не лезть куда-то еще. Например, сериализация полей, преобразование строки времени в DataTime и т.п. И никакой бизнес-логики. Только расширение возможностей модели.
Зато, можно улучшить жизнь другим разработчикам, которые пришли на поддержку проекта и не будут получать кучу лапши и и границы ответственности из мякушки хлеба.
То, что Вы называете сущностью - таковой не является. Выше, @Sam, написал, что любой объект, который можно идентифицировать. Если еще точней, то отражение какого-то реального объекта данных в нашем мире. А у Вас, @anton_z, просто ActiveRecord, где сразу и Entity есть, и Table Gateway, и Data Mapper, и Row Gateway и может даже еще что-то. Поэтому, Вы не правы в ваших высказываниях в сторону сущности. =) Entity пришел из реального мира, поэтому, с ним и нужно работать, как с объектом из реального мира.
Про анти-паттерн "Анемичные сущности" не слышали?
Мне кажется это все оттого, что в Doctrine ORM нет нормального способа внедрения зависимостей в сущности, оттуда и пошла эта анемия. Слишком уж они заигрались с ограничениями. Совсем уж не доверяют программистам).
Ха-ха, читал я и Эванса и Вернона, не надо мне советовать). Я уже через это прошел). Я для себя DDD понял так: DDD - это процесс проектирования до написания кода. А код неважно на чем, хоть на PL/SQL, хоть с DM, хоть без DM в DDD-подходе я могу написать. И у Эванса нигде не написано что DDD непосредственно подразумевает ООП.
Тому что я описал в DDD есть аналог - Domain Events. Они решают ту же задачу. Их тоже раскритикуете? Если все продумать, и сделать правильно, то такой ситуации не возникнет в принципе.
Это только ваше мнение, в Yii возможность использовать их как обработчики событий заложена изначально и подкреплена документацией.
Я считаю это глубоким заблуждением. Объекты в ООП - инструмент по связыванию состояния и поведения. При создании ООП из этого и исходили и так и нужно это воспринимать, а никак иначе. Чем ближе поведение к модифицируемому состоянию, тем лучше (с учетом, конечно, принципа Low Coupling). На описание юзкейса, конечно, нужно ориентироваться, но не абсолютизировать. Жизнь это жизнь, а программа это программа.
Ну и что? Мне удобно. В качестве сущностей можно использовать ActiveRecord, об этом даже у Фаулера в PoEAA написано.
Поверьте, жизнь программистов от этого никак не изменится. Как сталкивались с кучей лапши и писали кучу лапши, так и будут сталкиваться и писать. Это данность. Даже на супер-пупер ООП-продуманных Zend и Symfony с кучей ограничений полно проектов с лапшой.
Вопрос в снижении гибкости, если она есть и была заложена, люди пользовались, кому-то было удобно, зачем убирать? И у меня, например, это не приводило к лапше.
Это каждый сам для себя решает, что для него правильно, а что нет, я считаю, что это ошибка, вы можете с этим согласиться, а можете нет. А вот оценивать не надо. Оценивает нас работодатель, и оценивает он результат.
Я вижу в этом признаки шаблона Transaction Script. Когда все поведение в сервисах, а объекты содержат лишь данные, а не поведение, которое реализует бизнес-логику.
Я бы посмотрел на это с другой стороны, где содержатся данные (состояние), а где поведение. Если мы поведение убираем из сущностей, оно перейдет в сервисы (больше некуда). Сложное поведение будет разбиваться на несколько сервисов, вызывающих друг-друга. Фактически это процедурная декомпозиция. Вот и получится отдельно состояние системы, отдельно поведение. Я часто слышал - сервисы stateless, сущности stateful. А такое разделение у нас было в процедурном подходе и от которого ушли еще в конце 80-х, а теперь оно возвращается в другом обличье. Странно, не правда-ли?
Чем формулировка разделение на объекты хранящие данные, и на объекты реализующие поведение отличается от разделения на структуры данных и функции? Тем что все это написано со словами class и extends?). Я для себя решил, что ничем.
Слышал. Анти-паттернов-то много. Также слышал, что ООП нам не нужОн, и что это от лукавого. Не аргумент же =)
DM - Domain Model? Если так, то ведь в этом и суть DDD, без неё-то так? В целом, Вы правы, ограничений в парадигме нет.
Я не любитель использовать Domain Events, если честно. В одно время подумал, что этого достаточно для решения задач. И написал даже целый проект используя Domain Events. Это работало, конечно. Но! Мне не зашло. Вы ниже пишите про stateless/stateful дак вот такой подход мне заходит, и я его считаю как раз подходящим моему характеру: разделяй и властвуй, когда каждый занимается своей работой. Сейчас, Domain Events я только пару раз где-то в проекте запихал, опять же, только для расширения возможностей.
Да Вам никто не запрещает ведь. А вообще, ООП вытекло из императивной парадигмы. И я не вижу никакой проблемы или изъянов в том, чтобы где-то были анемии.
Можно, да. (Мне, лично, не удобно, как Вам)
Хороший шаблон же.
ООП вытек из императивной парадигмы. Для меня все закономерно, Вы видите это не так.
Кстати, нет никаких ограничений, чтобы применить Domain Events. Обработчики можно легко зарегистрировать, а достаточное количество событий у сущности позволяют запустить их.
Но сущность, ИМХО, должна оставаться без каких-либо зависимостей. Тогда все будет клева =)
Простите, шта? Соглашусь с anton_z, если сущность отражает запись в БД, то это Active Record, завязанная на конкретную реализацию БД. На мой взгляд это подмножество DM. Это не плохо, но иной подход к проектированию Data-driven design, что тоже уложится в те сокровенные DDD.
А Domain-driven design (DDD) проектирует систему с другой стороны, идет от домена к хранилищу. И, по идее, на выходе наш итоговый объект может собираться из нескольких, как минимум, таблиц, как максимум — хранилищ.
Но любую проблему нагляднее решать на примерах.
Есть записи и категории, их связь предлагаете вытаскивать в отдельный сервис?
Дальше. Есть некий продукт, например, хлеб, у него есть цена, цена реализована отдельной сущностью: значение, валюта... Чтобы получить цену, снова нужно дергать некий сервис?
@Ильдар, когда я писал о зависимостях, я имел ввиду внешние (о которых, собственно, и речь шла). А
внешние зависимости
!==связи сущности
. Не путайте.Под DM имел ввиду DataMapper. Прошу прощения, надо было точнее выразиться, во избежание путаницы.
А если мы, предположим, написали проект, а потом нам сказали, что нужно сделать чтобы при изменении пользователем таких-то атрибутов таких-то сущностей, должно происходить логгирование или отправка уведомления туда-то. Используя события это будет сделать очень просто и удобно, иначе придется обходить все сервисы, определять, какой сервис какие данные меняет и добавлять туда эти функции (или делать декораторы этих сервисов, если получится).
Так ООП было придумано на замену процедурному подходу, так как последний имеет ряд недостатков. Если у нас в программе есть классы, то это еще не говорит, что мы используем объектную парадигму, можно ведь и продолжать программировать в процедурном стиле, но с классами.
Анемию я считаю изъяном. Я представляю объектно-ориентированную программу как систему взаимодействующих объектов. Разделение на сущности и сервисы (stateful - stateless) воспринимаю как противоположный этому подход, так как там одни объекты активные (взаимодействуют), а другие пассивные (не взимодействуют, но позволяют с собой производить какие-то действия). В принципе я считаю его рабочим, так как он позволяет решать задачи и добиваться результата, однако отношусь к нему как к реинкарнации процедурного подхода к программированию. Я думаю, что выбор сводится здесь в сторону личных предпочтений. Ни то ни другое нельзя назвать плохим.
Да, но он опять же процедурный. У Фаулера об этом сказано.
Это объясняет желание разделять на stateless/stateful и положительное отношение к анемии.
А шину событий как в сущность прокинете, если в нее ничего внедрять нельзя? Это же зависимость.
Декораторы всегда получится сделать. И я не думаю, что нужно будет логировать все или делать уведомление для каждого сервиса. Плюсом, как Вы собрались логировать то, не зная что? У вас сущность, это Pure Object (ActiveRecord можно считать сущностью, но она такой в полной мере быть не может; здесь Ваш йии-стайл не пройдет), каких масштабов Вы собрались делать Hydrator и стратегию, чтобы все залогировать? Один большой неповоротливый комбайн получится. Плюсом, все, что Вы написали. это и должно быть в сервисе. Domain Events предполагает очевидность и добавить какой-то обработчик можно только явно. В любом случае, делать это придется в сервисе.
Вы в сущность ничего не внедряете, только регистрируете обработчик. А вот в обработчик идет инъекция всех нужных зависимостей. Сама сущность остается чистой и прозрачной.
В нашем случае мы работаем с контейнером, который хранит обработчики. Контейнер, это просто Map с методами attach() и detach() (или любые другие, по вкусу).
Вот пример кода поведения по логгированию любых изменений в приложении (CRM) по обработке клиентских заявок (модель Request). Логгируются все изменения, регистрируется время и пользователь, который внес изменения. Неповоротливых комбайнов не вижу), неважно в скольких сервисах будет использована AR Request с данным поведением, никаких изменений в них вносить не придется, сколько бы их ни было.
Request - это модель заявки, в данном контексте (не HTTP Request)
class RequestEventBehavior extends Behavior { public function events() { return [ ActiveRecord::EVENT_AFTER_INSERT => [$this, 'registerEvent'], ActiveRecord::EVENT_AFTER_UPDATE => [$this, 'registerEvent'], ActiveRecord::EVENT_BEFORE_DELETE => [$this, 'deleteEvents'] ]; } public function registerEvent(Event $event) { /** @var Request $sender */ $sender = $event->sender; Assert::isInstanceOf($sender, Request::class); $stored_event = new RequestEvent(); $stored_event->setAttributes($sender->getAttributes()); $stored_event->event_created_at = date('Y-m-d H:i:s'); $stored_event->event_created_by = $this->determineCreatedBy(); $stored_event->request_id = $sender->id; if (!$stored_event->save()) { if ($stored_event->hasErrors()) { $error = array_values($stored_event->getFirstErrors())[0]; } else { $error = 'Не удалось сохранить событие в БД.'; } throw new InvalidArgumentException($error); } } private function determineCreatedBy() { if (\Yii::$app->has('user') && !\Yii::$app->user->isGuest) { return \Yii::$app->user->id; } return null; } public function deleteEvents(Event $event) { /** @var Request $sender */ $sender = $event->sender; Assert::isInstanceOf($sender, Request::class); RequestEvent::deleteAll(['request_id' => $sender->id]); } }
Хорошо, а если нужно кастомное (например ActivatedEvent) событие заэмитить внутри сущности? Куда его предлагаете эмитить?
Не согласен с таким определением. Сущность это не Pure Object. (POPO, POJO). Что у Фаулера, что у Эванса, что у Вернона сущности должны содержать бизнес логику. Pure Object это объект с конструктором по умолчанию и геттерами/сеттерами, это не соответствует определению сущности.
В смысле "куда"? К слову, я бы вообще этого делать не стал, потому что для меня это плохой подход, но если очень хочется, то все точно также, как я и описал выше: аттачите в сущность и привет. В чем вопрос? В примере кода? Ну, к примеру, такой примитив:
Все. Что еще нужно? Unit of Work разберется дальше сам. И все чисто и хорошо. Занавес. ;)
Покажите, что должны, раз уж такая пьянка пошла.
Все верно. Идеальное решение для сущности: в конструкторе обязательные поля для заполнения, без которых вы получите Fatal Error. А в getters/setters могут быть полезные действия для значений.
Это вытекает из определения. Вот ссылка на определение сущности в википедии.
Нет я имел ввиду как затриггерить кастомное событие. Например в AR Yii я делаю:
class Request extends ActiveRecord { const EVENT_ACTIVATED = 'activated'; public function activate() { $this->activated = true; $this->save(); $this->trigger(self::EVENT_ACTIVATED); } }
А если у меня Doctrine ORM, я делаю, например, так (за неимением другого способа внедрения, приходится делать синглтон):
class Request { public function activate() { $this->activated = true; DomainEventPublisher::instance()->publish(new ActivatedEvent()); } }
Ммм... В En версии Вики такого нет. Там, как сказал @Sam выше. Кому будем верить? А давайте верить жизни? Ваш паспорт тоже сущность. У него есть идентификатор. И сам по себе он делать ничего не умеет. А МФЦ, прокуратура, полиция могут использовать Ваш паспорт на чтение и запись. Они сервисы. Логично? Да. Сущность, имхо, объект из реального мира.
Дак я и написал решение. Запись изменилась: выполняться все обработчики. Что Вы в них запихаете - дело Ваше:
Выше решение. Для Доктрины точно также.
Ну так это получится, что чтобы заэмитить какое-то новое событие нужно в каждом сервисе аттачить хэндлер, а это неудобно, менять все сервисы где данное событие может быть сгенерировано. Мне не нравится данное решение, для меня оно совсем не подходит, подход с behavior гораздо удобнее, он позволяет не вносить изменения в сервисы.
Не согласен с таким подходом в принципе. ООП это не жизнь. там все по своим правилам и принципам (GRASP, например). Объекты все могут иметь поведение, они все активные, а в жизни такого нет. Весь вопрос заключается в том, кому назначить поведение, я решаю его по GRASP. А рассуждения их жизни как раз и могут приводить к анемичным сущностям и жирным сервисам, им доверия у меня нет.
Фаулер об анемии
Пример Вернона
Там используется DomainEventPublisher - зависимость.
Неудобно потом ночью разбираться в проблемах и получать непредвиденное поведение. Все должно быть во время и когда надо. Разделяй и властвуй. Что может быть проще и надежней? Это, знаете, как если бы было несколько начальников, и подчиненные бы соотворяли хаос.
Это, конечно, прекрасно, имеет право на существование. Я такой подход не поощряю. Его, как минимум, сложно тестировать. Ну, и я думаю, это, все таки, пример ради примера. =)
Подход, который я описал никак не противоречит "Вовремя и когда надо". Я не испытываю подобных проблем. Проблемы у меня были как раз из-за необходимости просматривать кучу сервисов по нескольку раз, чтоб не забыть где-то подписаться на событие или, если без событий, залоггировать, отправить уведомление, по сути возникает дублирование кода добавления подписчика между сервисами.
А в чем именно возникает сложность при тестировании? Синглтон DomainEventPublisher можно и замокать но в этом даже нет нужды, так как он простой и можно в тесте проверить какие события в него были заброшены.
А если нужно новое событие добавить, которое должно генерироваться в каком-нибудь методе сущности, который вызывается из 10 сервисов? Я это решаю добавлением кода генерации события в один метод и кода обработки события в один behavior. Итого 2 класса. Если идти по пути подписывания в сервисах, придется внести изменения в 10 сервисах и сущности. Итого 11 классов.
Значит, что-то пошло не так. Можно всегда агрегировать. Есть правило трех в рефакторинге: когда приходится делать одно и тоже более 2х раз, на третий нужно агрегировать.
Если это функциональный тест, то да. Если модульный, то пляски с приседаниями обеспечены. Там может быть и работа с RabbitMQ или еще чем-то, что при модульном тестировании даже и не нужно учитывать. Хорошо бы его замокать, да, но раз это синглетон, то ... =)
То он прекрасно мокается. И в самом DomainEventPublisher не должно быть rabbitmq, он должен быть в подписчиках, которых в модульных тестах просто не будет.
Предлагаете агрегировать вызов одного метода? Одну строчку кода?
Количество строк не должно играть ключевой роли. Эта величина переменная и в любой момент их может стать больше.
Синглтон для domain events делать нельзя! Сначала событие кидаете, а потом в базу сохраняете? А если не сохранится? Или как в обработчике узнать автоинкрементный идентификатор сущности (вряд ли у вас UUID)? Нужно накапливать события в сущностях, а после успешной транзакции доставать ивенты из сущностей учавствоваших в ней. Не знаю, как в этих ваших AR такое делать, но с доктриной – изи.
Внедрять зависимости в сущности — le adoque. Используйте double dispatch и не связывайте сущности с инфраструктурой.
Не согласен. Можно. Я разруливаю описанную вами ситуацию с транзакциями двумя способами:
Если нужно железно гарантировать обработку события - сохраняю его сначала в ту же базу в той же транзакции. Потом другим процессом обрабатываю.
Если не нужно, в обработчике события ставлю еще один обработчик на событие завершения транзакции у подключения к БД, в котором и выполняю необходимые действия.
Если надо вытаскивать события из сущности снаружи, это делает сущность несамостоятельной, зависимой от того, вытащает из нее события после отработки или нет.
Вернон по-вашему, ошибается?
C AR это делается легко.
Не согласен с определением Double Dispatch, которое приведено по ссылке. Это вообще другое и связано с перегрузкой методов. Ссылка
Предпочту остаться при своем. Если зависимость широко используется в сущности внедряю через конструктор или SL в конструкторе, если в одном методе - через аргумент. Почему я не внедряю через методы всегда - это не удобно, надо везде таскать с собой зависимости, необходимые для вызова методов. К тому же, я считаю, что это нарушает инкапсуляцию: объект становится несамостоятельным, ему все дай да подай на каждый вызов.
Есть такой GRASPовский паттерн "информационный эксперт". Применяя его в том числе и для методов, можно научиться определять должен ли метод принадлежать сущности или нет.
Если изъять yii-стайл из Вашего кода (глобальщина везде и всюду), то Вы начнете создавать кошмарные решения в виде того, который Вы сделали для Доктрины. Вас спасает только Yii::$app. Не Yii, Вы бы изменили свой подход к разработке. И у Доктрины тоже есть AR.
Ошибаться могут все, мы же люди.
А вот не надо мне советовать. Сам разберусь что для меня лучше. И нет у меня глобальщины везде и всюду. Один объект Yii::$app - локатор сервисов. И то, дергаю его я либо в конструкторах, либо в init, и то чаще всего через Instanse::ensure, который еще и тип проверит. Ничего страшного для проекта он сам по себе не несет, все зависит от кривости рук разработчика. При правильном применении он дает гораздо большую гибкость и продуктивность, чем внедрение конструктора. Но это только для меня. Каждому свое.
На хабре в дайджесте выложили пост про анемию. Как раз в тему.
Ну если настолько серьезный бизнес – ок. У все проще, и если я потеряю какое-то событие никто не умрет, поэтому после любого флаша события сериализуются и улетают в рэббит, потом кто хочет – может подхватить и обработать.
Ок, как в этом всезнающем обработчике понимаете, что именно произошло и с какой сущностью?
Самое главное, что она сделала свои бизнес-операции и хочет уведомить внешний мир об этом. Достанет инфраструктура из нее события или нет – проблема инфраструктуры.
Да, это не идеальное и компромиссное решение, ради того, чтобы гарантировать подписчикам, что события придут после того, как мы все сохранили в БД и они получат консистентные данные.
Вот пример кода на yii1, из того, что было под рукой:
// Это поведение обрабатывает событие afterSave у AR Message class TransportMessageBehavior extends \CActiveRecordBehavior { /** * @var Transport */ private $transport; /** * TransportMessageBehavior constructor. */ public function __construct() { $this->transport = \Yii::app()->getComponent('transport'); Assert::isInstanceOf($this->transport, Transport::class); } /** * @param \CEvent $event * @throws \CException */ public function afterSave($event) { /** @var Message $message */ $message = $event->sender; Assert::isInstanceOf($message, Message::class); if ($message->isNewRecord) { $this->publishAppend($message); } } /** * @param Message $message * @throws \CException */ private function publishAppend(Message $message) { /** @var Connection $connection */ $connection = $message->getDbConnection(); Assert::isInstanceOf($connection, Connection::class); $connection->attachOnCommitHandler(function() use ($message) { $this->transport->publishAppend($message); } }); } } class Connection extends \CDbConnection { /** * @param callable $callable * @throws \CException */ public function attachOnCommitHandler(callable $callable) { $transaction = $this->getCurrentTransaction(); if ($transaction != null) { $transaction->attachEventHandler(Transaction::EVENT_ON_COMMIT, $callable); } else { $event = new \CEvent($this); call_user_func($callable, $event); } } }
Естественно, если транзакция фейлится, обработчики не когда не будут вызваны, Connection/Transaction в yii1 из коробки не имеет событий, унаследовались.
И что?! Суть та же, но без перегрузки методов. Самое главное, что этот подход позволяет локализовать использование зависимостей до конкретных операций и делать это явно!
Проблема и цели разные. Из википедии:
Там фишка в том, что при разных типах аргументов вызываются разные методы. Подходит для чего-то типа:
Смысл в том, что клиентскому коду реальные типы не известны, только базовые. При этом двойная диспетчеризация поможет компилятору определить нужный метод, основываясь не только на типе объекта, метод которого вызывается, но и на типе аргумента.
А в той статье что вы представили, совсем другая цель - DI.
Поддерживаю!) GRASP для этой задачи самое то.
Везде где тема касается DDD начинается холивар. Люди начитавшись одних и тех же книг начинают доказывать что они правильнее поняли, чем другие) Загнали себя в рамки и спорят,что его рамка лучше, чем у оппонента.
Согласен с комментарием №11341. Именно в точку. Жесть, что творится.
Никоим образом не хочу вас обидеть, но вы бы там полегче-то. Вы или читали не то, или не поняли что читали.
Doctrine ORM она не про DDD. Она про DataMapper pattern. И Entity в ее рамках это всего лишь Data model и совсем не равна Domain model
Скажу где конкретно видел, что сущности Doctrine используются в качестве DomainModel - книга DDD in PHP.
Блог
У Вернона, помоему, в качестве слоя взаимодействия с БД используеся Hibernate, его сущности используются в качестве моделей в домене.
Вы все это опровергаете?
Да, но DataMapper как раз и используется для сохранения сущностей домена в БД. А вы делаете отдельные классы для DomainModel и DataModel (например, ProductDataModel и Product)? Я слышал о таком подходе и пробовал его. Мне это показалось крайне избыточным, кучу мэппингов надо писать, гораздо дольше код пишется.
Вообще для себя DataMapper и Donctrine (как реализацию) считаю избыточными и неудобными инструментами, ActiveRecord (Yii, Eloquent) для меня куда лучше.
Для этого нужно Entity перегружать дополнительной логикой. Со временем они толстеют. Добавьте сюда логику отображения БД на объекты и получите еще больше сложности в рамках одного класса.
Для решения этих вопросов в Doctrine и разделяется манипуляция с данными и сами данные.
А они и избыточны на большинстве проектов.
Но с определенного уровня сложности проекта ActiveRecord становится совсем не вариантом - класс вырастает в нечто монструозное.
А вот с определенного это с какого? Как вы это определяете, Очень интересно. С толстением AR я борюсь используя события и классы-обработчики событий и мне удается сохранить AR не перегруженными.
Я не понял, что вы имеете ввиду. Как вы все-таки предлагаете делать? Использовать сущности Doctrine в качестве сущностей домена или делать отдельно сущности Doctrine и потом маппить их на сущности домена?