

Dmitry
24.02.2018
08:09:46
Такс по пунктам:
1. Никто нигде ничего не обязан. Делайте как вам удобно, но не вводите людей в заблуждение что это SOLID, DDD или что-то еще.
2. "мне нельзя в AR добавить ООП" ну тут я даже не знаю как комментировать...
3. "что так никак нельзя делать в AR или чём то ещё и можно лишь в их православном DM" внезапно PI никоим образом не говорит использовать DM или что-то еще, будь то AR, R(TD)G и иже с ними. Просто ваш репозиторий не несет никакой пользы. Он ничего не инкапсулирует "от слова совсем" (с) Максим. PI поверх AR пилится вполне себе легко и доступно. Собсно сам так жил с Propel лет так 5 назад. Достаточно:
a) Выделить интерфейс репозитория и положить его рядом с доменной моделью. А уже в Yii будет лежать имплементация аля ActiveRecordUserRepository.
б) Разделить доменную модель и AR модель и гидрировать доменную из AR (и наоборот) в том же репозитории. Для примера ваш UserRepository::findByEmail() будет выглядеть как-то так:
`
function findByEmail(string $email): Shop\User
{
// В AR классах оставляем всю yii муть
$userRecord = UserRecord::getByEmail($email);
$user = new Shop\User($userRecord->name, $userRecord->email, ...);
$this->uow->registerClean($user); // Если мы не хотим вызывать явно UserRepository::save();
return $user;
}
3. "И готовы бить морду за то что "нет контекстов, значит код говно": дело не в том что их нет, дело в том когда они должны появится. И вроде вы на это уже ответили. Зачем возвращаться?
4. "Ну и не читают диагональщики ..." кто и что читает личное дело каждого. Поверьте ничего нового вы мне не открыли. ЗЫ. В статье годно вполне расписано все.
1. Не вводите всех в заблуждение, что я ввожу всех в заблуждение. Здесь именно остальные участники случайно нашли мой код, сказали что это DDD, потом целые сутки полоскали друг другу и мне мозг, что это "неправильный DDD" и там "всё говно". Я, наоборот, всех из заблуждения вывожу, говоря что "это не их DDD, а просто проект на фреймворке с сервисным слоем".
2. См. пункт 3.
3. Этот класс инкапсулирует "грязные" вызовы поиска и сохранения сущности, выкидывания исключений и эмита событий за "чистым" интерфейсом, вынося эти манипуляции из сервисного слоя. Это не от слова "совсем". С таким же успехом могу сказать, что ваш DoctrineUserRepository тоже не приносит никакой пользы, так как только дёргает $this->em. Повторюсь: если не нравится именно название UserRepository, то назовите его как-нибудь как UserFinderAndSaveCaller.
А то, что ДОМЕННЫЕ СУЩНОСТИ ОБЯЗАТЕЛЬНО должны быть ЧИСТЫМИ и что НЕЛЬЗЯ эти же ДОМЕННЫЕ СУЩНОСТИ делать прямо на базе ActiveRecord или Propel, поместив в них те же доменные методы и прописав в них тот же маппинг тех же VO внутри в их afterFind и beforeSave - это как раз религиозный DDD-маразм, возникающий от чтения всего по диагонали.
Еще забыли добавить пункты в) сделать прокси для связей и коллекций и г) спрограммировать обход этого хозяйства для отслеживания изменений.


Anton
24.02.2018
08:27:31
Ничего он не инкапсулирует, как можно раньше было вызвать напрямую User::save() в обход него так и сейчас можно. Смысл от этого UserFinderAndSaveCaller?
По поводу сущностей и нельзя, оно то можно только смысл? Как минимум у класса уже нарушен SRP. А при наличии большего количества логики поддерживать это сомнительное удовольствие.

Dmitry
24.02.2018
08:47:58

Google

Anton
24.02.2018
09:01:20
Почему мой? У меня его нет например.
И вы утрируете, а не дискутируете. Технические ограничения всегда будут, я говорю о том что пользы от конкретного класса у вас нет и только.

Maxim
24.02.2018
09:11:25

Sergey
24.02.2018
09:11:57
хотя это конечно больше про CQS

Maxim
24.02.2018
09:12:14

Anton
24.02.2018
09:12:15
Он и не создает

Sergey
24.02.2018
09:12:16
и это не всегда возможно

Maxim
24.02.2018
09:12:24
так в чем суть репозитория?

Sergey
24.02.2018
09:12:47
и все

Anton
24.02.2018
09:12:50
Чтобы инкапсулирувовать логику получения пользователя из хранилища

Google

Maxim
24.02.2018
09:13:12
Он и не создает
$user = new Shop\User($userRecord->name, $userRecord->email, ...);
$this->uow->registerClean($user);
А это что происходит?

Sergey
24.02.2018
09:13:20

Anton
24.02.2018
09:14:06

Sergey
24.02.2018
09:14:08

Maxim
24.02.2018
09:14:28
какая-то чернь
почему вы не оспариваете этот пример от @zloyuser ? это же чернь в чате про нормальное ООП

Sergey
24.02.2018
09:14:58
@zloyuser это ты типа заворачиваешь данные в модельку и менеджет ее делаешь?

Anton
24.02.2018
09:15:49

Sergey
24.02.2018
09:16:05
а

Anton
24.02.2018
09:16:36
Доменная модель отдельно, модель данных отдельно

Sergey
24.02.2018
09:16:46
если я правильно понял @zloyuser
меня просто нэйминг смущает)

Anton
24.02.2018
09:17:02
Все верно

Sergey
24.02.2018
09:17:45
дальнейший шаг будет гидрация через рефлексии, что бы сущность юзера понятия не имела о том что с момента ее инициализации в первый раз ее многократно загружают из базы
причем внутри сущности мы можем и вообще AR модельки юзать

Anton
24.02.2018
09:18:42
Как вариант. Ну а дальше уже разделение моделей чтения-записи.
AR вообще то отличный паттерн, если его приготовить

Maksim
24.02.2018
09:19:33
приготовить на выброс)

Google

Anton
24.02.2018
09:20:46
Ну года так три-четыре назад я бы тоже сказал. Сейчас вижу много кейсов использования.

Sergey
24.02.2018
09:21:11
AR как по мне вообще отлично работает на чтение... прям замечательно.

Maksim
24.02.2018
09:21:21
при 1 большом условии

Sergey
24.02.2018
09:21:23
просто, быстро, удобно

Maxim
24.02.2018
09:21:26

Anton
24.02.2018
09:21:45
кинуть исключение, вернуть null

Sergey
24.02.2018
09:21:50

Maxim
24.02.2018
09:22:21

Anton
24.02.2018
09:22:23
мне привычно когда find* возвращает или инстанс или null, тогда как get* или инстанс или кидает исключение

Sergey
24.02.2018
09:22:42

Anton
24.02.2018
09:23:14
function findByEmail(string $email): ?Shop\User
{
// В AR классах оставляем всю yii муть
if (!$userRecord = UserRecord::findByEmail($email)) {
return null
}
$user = new Shop\User($userRecord->name, $userRecord->email, ...);
$this->uow->registerClean($user); // Если мы не хотим вызывать явно UserRepository::save();
return $user;
}

Sergey
24.02.2018
09:23:54
осталось еще быстрый рецепт как быстро написать UoW + IM

Sergey
24.02.2018
09:24:55
я как-то накидывал Макарову на тему "а почему никто не делает AR с инвертированной иерархией наследования", ну мол с прокси классами и т.д.
можно было бы сделать прикольно

Anton
24.02.2018
09:25:13
Хороший UoW это не быстро в PHP. Много нюансов с клонами, сериализацией и прочее. Монстр доктрины тому подтверждение.

Sergey
24.02.2018
09:25:41
где UserRecord это какой-то сгенерированный прокси класс

Anton
24.02.2018
09:26:30

Google

Sergey
24.02.2018
09:26:59
у фаулера так и написано - что мол "универсальный UoW это круто конечно но оч сложно и часто не имеет практической пользы")

Anton
24.02.2018
09:27:19
т.е. AR класс будет наследовать (имплементить) доменный?

Sergey
24.02.2018
09:27:34
блин я уже не помню, год назад было
но да, идея была в том что бы мувнуть персистентность на уровень выше
что бы можно было подменять удобнее

Admin
ERROR: S client not available

Anton
24.02.2018
09:28:08
Интересная мысль, но надо переварить

Maxim
24.02.2018
09:28:34

Anton
24.02.2018
09:28:39
Такие штуки надо под пиво обсуждать... Хотели же собраться, и что-то идея заглохла

Sergey
24.02.2018
09:28:59

Anton
24.02.2018
09:29:11
https://martinfowler.com/eaaCatalog/unitOfWork.html

Sergey
24.02.2018
09:30:43
ну да, так проще

Anton
24.02.2018
09:30:45
Это нужно чтобы не вызывать вручную User::save(). В конце запроса (команды, бизнес транзакции, etc.) UoW проверяет какие объекты изменились и сохраняет изменения в базу.
Это в двух словах
Не всегда это нужно и не всегда это удобно + как уже говорилось выше достаточно сложно в имплементации с нуля.
P.S. Если ты не Сергей Протько конечно :))

Maxim
24.02.2018
09:32:30

Anton
24.02.2018
09:32:44
Doctrine ORM = DBAL + IdentityMap + DataMapper + UnitOfWork

Dmitry
24.02.2018
09:45:30

Google

Anton
24.02.2018
09:59:24
Претензия только одна: я пришел в ваш код и вызвал User::save() напрямую и пропали наши доменные ивенты. Ваш "репозиторий" (вообще то это скорее фасад если его задача только скрыть внутренние вызовы) никак не защищает от этого. Вариант с отдельной моделью решает такой кейс.

Maxim
24.02.2018
10:02:11

Maksim
24.02.2018
10:02:28

Anton
24.02.2018
10:02:32
Именно
Нет я понимаю что это можно решать соглашениями и прочее, но зачем если это не сложно явно выразить в коде?

Maxim
24.02.2018
10:09:44

Anton
24.02.2018
10:11:07
Вот я прям предвидел такой наброс. Так и изменения агрегата не было с чего вы взяли что должы быть ивенты?

Maxim
24.02.2018
10:12:16

Anton
24.02.2018
10:12:42
По сути да, как выше Дмитрий и сказал при желании можно обойти любой код. Прямой запрос в базу, Reflection, Runkit да что угодно...

Maxim
24.02.2018
10:14:42
Тогда я не пойму притензий к Дмитрию, что в любом месте можно вызвать $some->save()
Претензия только одна: я пришел в ваш код и вызвал User::save() напрямую и пропали наши доменные ивенты

Anton
24.02.2018
10:16:21
В моем варианте ответственности отделены: доменная модель умеющая защиту инвариантов, генерацию событий и иже с ним проста и понятна. И для ее получения (и сохранения если нам лень в UoW) есть репозиторий. Работа с данными вынесена в отдельный объект про который конечный юзер (наш джун) не знает.

Sergey
24.02.2018
10:16:30

Anton
24.02.2018
10:17:47
Более того класс UserRecord может вообще не существовать в дев окружении, а генерироваться только при сборке. И вызвать его напрямую не получится.

Sergey
24.02.2018
10:17:53
при том что всю ту де инкапсуляцию/абстракцию можно было бы сделать не сильно усложняя код и не вводя дополнительные програмные сущности