Проектирование сущностей, сервисов и репозиториев

Обсуждаем, как правильно строить приложения
zelenin
Сообщения: 10043
Зарегистрирован: 2013.04.20, 11:30

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение zelenin » 2017.04.07, 14:19

Код: Выделить всё

 public function rename(EmployeeId $id, NameDto $dto)
    {
        $employee = $this->employees->get($id);
        $employee->rename(new Name(
            $dto->last,
            $dto->first,
            $dto->middle
        ));
        $this->employees->save($employee);
        $this->dispatcher->dispatch($employee->releaseEvents());
    }
события все же кидать в диспетчер лучше непосредственно в репозитории, в методах где происходят изменения - так достигнем атомарности "сохранение=>события".

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.07, 15:17

zelenin писал(а):
2017.04.07, 14:19
события все же кидать в диспетчер лучше непосредственно в репозитории, в методах где происходят изменения - так достигнем атомарности "сохранение=>события".
Да, но если вызываем сохранение у несколько репозиториев (обернув в TransactionManager или TransactionalCommandBus) то это не прокатит.
Не забудьте пройти мастер-класс по Yii2.


zelenin
Сообщения: 10043
Зарегистрирован: 2013.04.20, 11:30

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение zelenin » 2017.04.07, 15:28

ElisDN писал(а):
2017.04.07, 15:17
zelenin писал(а):
2017.04.07, 14:19
события все же кидать в диспетчер лучше непосредственно в репозитории, в методах где происходят изменения - так достигнем атомарности "сохранение=>события".
Да, но если вызываем сохранение у несколько репозиториев (обернув в TransactionManager или TransactionalCommandBus) то это не прокатит.
почему? каждый репозиторий кинет свои события, сохранив свои сущности. (про атомарность я в контексте одного места сохранения/релиза событий, а не про транзакции).

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.07, 15:35

zelenin писал(а):
2017.04.07, 15:28
почему? каждый репозиторий кинет свои события, сохранив свои сущности.
Первые два кинут, а третий вылетит с RuntimeException и всё откатит:

Код: Выделить всё

$this->transactionManager->execute(function () use ($interview, $employee, $contract) {
    $this->employeeRepository->add($employee);
    $this->contractRepository->add($contract);
    $this->interviewRepository->save($interview);
});
а события из первых двух уже ушли...
Не забудьте пройти мастер-класс по Yii2.

zelenin
Сообщения: 10043
Зарегистрирован: 2013.04.20, 11:30

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение zelenin » 2017.04.07, 15:48

это собственно обычная проблема при смешивании транзакций с нетранзакционными вещами. В данном случае проблема в слишком синхронном диспетчере. Решить проблему можно а) накоплением пула эвентов и выполнением их после выполнения основной транзакции, б) персистом эвентов, в) асинхронной шиной.
Тут же получается протечка знания о реализации одного слоя в реализацию другого слоя.

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.07, 17:31

zelenin писал(а):
2017.04.07, 15:48
это собственно обычная проблема при смешивании транзакций с нетранзакционными вещами.
С TransactionalCommandBus это решается подсовыванием DefferedEventDispatcher с накопительным методом dispatch(), чтобы после успешного выполнения хэндлера в транзакции запускать обработку вызовом $dispatcher->relaseAll(). А при работе без шины (напрямую с сервисом) это реализовать уже проблематично.
Не забудьте пройти мастер-класс по Yii2.

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.08, 13:28

Добавил третью часть с реализацией репозитория.
Не забудьте пройти мастер-класс по Yii2.

glagola
Сообщения: 47
Зарегистрирован: 2017.02.22, 19:43

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение glagola » 2017.04.08, 14:53

Я DDD увлекся иключительно благодаря данному форуму! А теперь есть еще и детальные статьи раскрывающие тему, спасибо вам!

Вот из моей небольшой практики, относительно методов add и save из вашей статьи:

Код: Выделить всё

interface EmployeeRepository
{
    ....
    /**
     * @param Employee $employee
     */
    public function add(Employee $employee);
 
    /**
     * @param Employee $employee
     */
    public function save(Employee $employee);
    ....
}
У меня один метод "save" в репозиториях, а понять insert/update можно двумя способами:
  • Если у вас в IdentityMap хранятся все загруженные сущности в рамках текущего запроса, то, при вызове метода save, можно проверить есть ли в IdentityMap сохраняемый объект: есть (update), нету (insert)
  • Сохранять все сгенерированные nextId() в отдельный массив в репозитории, чтобы при сохранении реализовать логику аналогичную первому варанту.

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.08, 15:56

glagola писал(а):
2017.04.08, 14:53
Если у вас в IdentityMap хранятся все загруженные сущности
Да, но только если есть IdentityMap.
Не забудьте пройти мастер-класс по Yii2.

glagola
Сообщения: 47
Зарегистрирован: 2017.02.22, 19:43

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение glagola » 2017.04.11, 19:14

Цитата из статьи про репозитории :
Контейнер Yii написан так, что Instance::of может принимать не только имена элементов контейнера, но и сервис-локатора Yii::$app, поэтому мы можем указать напрямую Instance::of('db') и он поймёт, что мы от него хотим именно объект Yii::$app->db
Я, когда это увидел, обрадовался, что больше не нужно лепить лямбды, чтобы заинжектить \Yii::$app->db, но когда попробовал в тестовом экшене консольного контроллера, задекларировать, а потом и создать:

Код: Выделить всё

\Yii::$container->setSingleton(
    UserMembershipDetailsRepository::class,
    UserMembershipDetailsRepository::class,
    [Instance::of('db')]
);
$repository = \Yii::createObject(UserMembershipDetailsRepository::class);
Получил ошибку
Exception 'ReflectionException' with message 'Class db does not exist'
Вы точно, нигде не задекларировали db в DI-конейнере? как-то так:

Код: Выделить всё

\Yii::$container->setSingleton('db', function () {
    return \Yii::$app->db;
});
Потому как если он заранее задекларирован, то все работает.

P.S. версия Yii 2.0.11.2

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.11, 20:03

glagola писал(а):
2017.04.11, 19:14
Получил ошибку 'Class db does not exist'
А в консольной конфигурации компонент 'db' настроен?
Не забудьте пройти мастер-класс по Yii2.

glagola
Сообщения: 47
Зарегистрирован: 2017.02.22, 19:43

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение glagola » 2017.04.11, 21:39

А в консольной конфигурации компонент 'db' настроен?
Не думаю что это имеет отношение к делу (другое дело если бы контейнер вернул не инициализированный инстанс Connect), но, в любом случае, если вы имеете ввиду, настроено ли подключение к БД - да у него рабочая конфигурация.

Вот так работает:

Код: Выделить всё

\Yii::$container->setSingleton('db', function () {
    return \Yii::$app->db;
});
\Yii::$container->setSingleton(
    UserMembershipDetailsRepository::class,
    UserMembershipDetailsRepository::class,
    [Instance::of('db')]
);
$repository = \Yii::createObject(UserMembershipDetailsRepository::class);
А вот так нет:

Код: Выделить всё

\Yii::$container->setSingleton(
    UserMembershipDetailsRepository::class,
    UserMembershipDetailsRepository::class,
    [Instance::of('db')]
);
$repository = \Yii::createObject(UserMembershipDetailsRepository::class);
В комментариях к классу yii\di\Instance есть пример, который, собственно, и натолкнул меня на мысль, что вы еще где-то прописали db.

P.S. есть еще одно отличие от вашего демо-github-репозитория - у меня Advanced application template, хотя я не думаю, что такие core фичи могут различаться.

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.11, 22:15

glagola писал(а):
2017.04.11, 21:39
...
Посмотрел в исходники и увидел, что из Yii::$app он дёргает только в `Instance:ensure`. Поменял у себя тоже на:

Код: Выделить всё

$container->setSingleton('db', function () use ($app) {
    return $app->db;
});

$container->setSingleton(EmployeeRepository::class, SqlEmployeeRepository::class, [
    Instance::of('db'),
]);
Не забудьте пройти мастер-класс по Yii2.

Аватара пользователя
pistol
Сообщения: 214
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение pistol » 2017.04.17, 13:16

А если у сущности Employee 30 полей и каждый месяц добавляется новое из того, что "забыли" при проектировании? Через конструтор все передавать и каждый раз не забывать во всех других сервисных классах добавлять передачу в конструтор? Или есть какая-то хорошая практика для таких сложных сущностей (кроме передачи "черного" массива в конце))) ?

Аватара пользователя
slavcodev
Сообщения: 3133
Зарегистрирован: 2009.04.02, 21:42
Откуда: Altea, Spain
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение slavcodev » 2017.04.17, 17:24

Есть хорошие практики, любой патерн из категории Creational patterns
Жду Yii 3!

Аватара пользователя
pistol
Сообщения: 214
Зарегистрирован: 2014.07.12, 15:18
Откуда: Курган
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение pistol » 2017.04.17, 17:52

Спасибо. Попробую попрактиковаться на боевом примере.

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.04.21, 09:32

Добавил новую часть про Doctrine.
Не забудьте пройти мастер-класс по Yii2.

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей, сервисов и репозиториев

Сообщение ElisDN » 2017.05.07, 11:50

Добавил часть про ActiveRecord.
Не забудьте пройти мастер-класс по Yii2.

Аватара пользователя
ElisDN
Сообщения: 4524
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Проектирование сущностей предметной области

Сообщение ElisDN » 2017.10.12, 11:51

sda писал(а):
2017.04.01, 06:18
Наверное имеет ввиду отсутствие транзакций в nosql. Но мне интересно, для чего Дмитрий бы их использовал в nosql, ведь Вон Вернон пишет, что сохранять нужно не более 1 агрегата внутри транзакции. Но в nosql мы можем и без транзакции атомарно сохранить весь агрегат одной целостной json структурой. Тогда для решения какой проблемы нужны транзакции в nosql ?
При оформлении заказа в магазине с подарочным сертификатом + балансом нужно:

- Создать и сохранить заказ
- Списать наличие и пересохранить в цикле все товары
- Списать с баланса и пересохранить аккаунт
- Закрыть и пересохранить сертификат
- Начислить бонусных баллов и пересохранить профиль
- Пересчитать рейтинг продавца и пересохранить профиль
- Начислить комиссию партнёру и пересохранить профиль
- ...

Это уже десятки json-структур, а не одна.
Не забудьте пройти мастер-класс по Yii2.

Ответить