Сервисный слой, как правильно?
Re: Сервисный слой, как правильно?
viewtopic.php?f=19&t=36725&e=1&view=unread#p188512
по п.3: реализация базовая - нужно учесть, что есть ситуации, когда необходимо ресолвить и зависимости зависимостей.
по п.3: реализация базовая - нужно учесть, что есть ситуации, когда необходимо ресолвить и зависимости зависимостей.
Re: Сервисный слой, как правильно?
Можно UserServiceInterface упразднить вообще и разбить его на хендлеры (UserRegistrationHandler,UserLoginHandler и т.д.)?zelenin писал(а):сервис, имеющий значение для домена - генератор id, хэшер паролей, расчет комиссии - то, о чем знает домен, и что является непосредственной частью домена.Melodic писал(а):Если UserServiceInterface относится к Application, то что тогда может являться доменным сервисом на примере пользователя?
UserServiceInterface - это сервис, который данные из реквеста, преобразует в данные для домена с использованием некоей логики - другой слой. Реквест не является частью домена.верно. dto превращаются в Command, а сервисы в хэндлеры.Melodic писал(а):По CommandBus. Т.е. хендлеры по сути являются сервисами слоя Application?
Re: Сервисный слой, как правильно?
Можно же просто ProxyManager подключить (вы ссылку давали на github), там вроде ничего сложного нет.zelenin писал(а):viewtopic.php?f=19&t=36725&e=1&view=unread#p188512
по п.3: реализация базовая - нужно учесть, что есть ситуации, когда необходимо ресолвить и зависимости зависимостей.
Re: Сервисный слой, как правильно?
Либо он не будет instanceof User либо не будет работать с final. Попробуйте, расскажите. У меня пока времени нет на практические исследования.Melodic писал(а):Можно же просто ProxyManager подключить (вы ссылку давали на github), там вроде ничего сложного нет.zelenin писал(а):viewtopic.php?f=19&t=36725&e=1&view=unread#p188512
по п.3: реализация базовая - нужно учесть, что есть ситуации, когда необходимо ресолвить и зависимости зависимостей.
Re: Сервисный слой, как правильно?
Ещё по CommandBus вопрос.
CommandBus после выполнения команды не должна же ничего возвращать? Как тогда узнать об успешном выполнении? К примеру о успешном выполнении входа? С помощью событий?
CommandBus после выполнения команды не должна же ничего возвращать? Как тогда узнать об успешном выполнении? К примеру о успешном выполнении входа? С помощью событий?
Re: Сервисный слой, как правильно?
все, что не может вернуть результат, может выкинуть исключение при ошибкеMelodic писал(а):Ещё по CommandBus вопрос.
CommandBus после выполнения команды не должна же ничего возвращать? Как тогда узнать об успешном выполнении? К примеру о успешном выполнении входа? С помощью событий?
Re: Сервисный слой, как правильно?
Да, так и делают. Вместо держания десятков методов в огромном UserService каждый операционный метод сервиса:Melodic писал(а):Можно UserServiceInterface упразднить вообще и разбить его на хендлеры (UserRegistrationHandler,UserLoginHandler и т.д.)?
Код: Выделить всё
class UserSignupDto { ... }
class UserService
{
public function signup(UserSignupDto $dto) { ... }
public function update(UserUpdateDto $dto) { ... }
...
public function ban(UserBanDto $dto) { ... }
}
Код: Выделить всё
$this->userService->signup(new UserSignupDto($form->email, $form->password));
Код: Выделить всё
class UserSignupCommand { ... }
class UserSignupHandler
{
public function handle(UserSignupCommand $command) { ... }
}
Код: Выделить всё
Command
User
Signup
UserSignupCommand.php
UserSignupHandler.php
UserSignupValidator.php
Ban
UserBanCommand.php
UserBanHandler.php
UserBanValidator.php
Код: Выделить всё
$this->commandBus->execute(new UserSignupCommand($form->email, $form->password));
Код: Выделить всё
interface CommandBusInterface
{
public function execute($command);
}
Код: Выделить всё
class SimpleCommandBus implements CommandBusInterface
{
public function execute($command)
{
$handler = $this->resolveHandler($command);
call_user_func($handler, $command);
}
private function resolveHandler($command)
{
return [\Yii::createObject(substr(get_class($command), 0, -7) . 'Handler'), 'handle'];
}
}
Код: Выделить всё
class UserSignupValidator
{
private $userRepository;
public function __construct(UserRepositoryInterface $userRepository)
{
$this->userRepository = $userRepository;
}
public function validate(UserSignupCommand $command)
{
$errors = [];
...
if ($this->userRepository->existsByUsername($command->username)) {
$errors['username'][] = 'This username has already been taken.';
}
if ($this->userRepository->existsByEmail($command->email)) {
$errors['email'][] = 'This email has already been taken.';
}
return $errors;
}
}
Код: Выделить всё
class ValidationCommandBus implements CommandBusInterface
{
private $next;
public function __construct(CommandBusInterface $next) {
$this->next = $next;
}
public function execute($command) {
$validator = $this->resolveValidator($command);
if ($errors = call_user_func($validator, $command)) {
throw new ValidationException($errors);
}
$this->next->execute($command);
}
private function resolveValidator($command) {
return [\Yii::createObject(substr(get_class($command), 0, -7) . 'Validator'), 'validate'];
}
}
Код: Выделить всё
class LogCommandBus implements CommandBusInterface
{
private $next;
public function __construct(CommandBusInterface $next) {
$this->next = $next;
}
public function execute($command) {
Yii::info('Executing of ' . get_class($command), 'bus');
$this->next->execute($command);
}
}
Код: Выделить всё
$container->setSingleton('app\commands\CommandBusInterface', function () {
return new LogCommandBus(new ValidationCommandBus(new SimpleCommandBus()));
});
Но и от геттеров подобным образом можно избавиться, если рядом с CommandBus аналогично сделать QueryBus:
Код: Выделить всё
$newUsers = $this->queryBus->query(new NewUsersQuery(10));
Вот, собственно, весь полноценный рабочий CommandBus мы здесь в одном комментарии и сочинили.
Последний раз редактировалось ElisDN 2016.08.05, 23:02, всего редактировалось 4 раза.
Re: Сервисный слой, как правильно?
Как будет правильно сделать?zelenin писал(а):это ок. в ddd есть понятие bounded context (сущность со всеми связями) - репозиторий создается именно для bounded context.Melodic писал(а):С репозиториями не много не понятно.
По вашему совету, все методы для сохранения\добавления\обновления БД я вынес в репозиторий UserRepository.
У модели User есть поле $city, которое содержит модель City.
В репозитории UserRepository есть метод findById($id), который ищет пользователя по Id и заполняет модель User.
Проблема в том, что поле $city остаётся не заполненным. Джоинить таблицу `city` прям в методе UserRepository::findById($id) и там создавать модель City? Но это, вроде, не правильно, что UserRepository оперирует моделью City. Как быть в таком случае?
Передавать CityRepositoryInterface ( у которого есть метод ::load($array) который возвращает модель) в UserRepository и с помощью CityRepositoryInterface::load() загружать модель City?
Re: Сервисный слой, как правильно?
я спутал термины - bounded context => aggregate root
"это ок. в ddd есть понятие aggregate root (сущность со всеми связями) - репозиторий создается именно для aggregate root."
"это ок. в ddd есть понятие aggregate root (сущность со всеми связями) - репозиторий создается именно для aggregate root."
Re: Сервисный слой, как правильно?
вчера провел аналитику проксирования - ни окрамиус ни доктрина не проксируют final.
Re: Сервисный слой, как правильно?
что такое load? что делает внутри? массив (строка из базы) в модель?Melodic писал(а):Как будет правильно сделать?zelenin писал(а):это ок. в ddd есть понятие bounded context (сущность со всеми связями) - репозиторий создается именно для bounded context.Melodic писал(а):С репозиториями не много не понятно.
По вашему совету, все методы для сохранения\добавления\обновления БД я вынес в репозиторий UserRepository.
У модели User есть поле $city, которое содержит модель City.
В репозитории UserRepository есть метод findById($id), который ищет пользователя по Id и заполняет модель User.
Проблема в том, что поле $city остаётся не заполненным. Джоинить таблицу `city` прям в методе UserRepository::findById($id) и там создавать модель City? Но это, вроде, не правильно, что UserRepository оперирует моделью City. Как быть в таком случае?
Передавать CityRepositoryInterface ( у которого есть метод ::load($array) который возвращает модель) в UserRepository и с помощью CityRepositoryInterface::load() загружать модель City?
Re: Сервисный слой, как правильно?
load() создаёт модель и наполняет её с помощью set*() данными из $array, $array обычный массив [$column_db=>$value]zelenin писал(а):что такое load? что делает внутри? массив (строка из базы) в модель?Melodic писал(а):Как будет правильно сделать?zelenin писал(а): это ок. в ddd есть понятие bounded context (сущность со всеми связями) - репозиторий создается именно для bounded context.
Передавать CityRepositoryInterface ( у которого есть метод ::load($array) который возвращает модель) в UserRepository и с помощью CityRepositoryInterface::load() загружать модель City?
Код: Выделить всё
public function load( $result)
{
$entity = new User();
$entity->setId($result['id']);
$entity->setFirstName($result['first_name']);
$entity->setLastName($result['last_name']);
$entity->setPatronymic($result['patronymic']);
$entity->setEmail($result['email']);
$entity->setPasswordHash($result['password_hash']);
$entity->setAbout($result['about']);
$entity->setRegistrationDate(new \DateTime($result['registration_date']));
$entity->setLastVisitDate(new \DateTime($result['last_visit_date']));
$entity->setLastVisitIP($result['last_visit_ip']);
$entity->setRegistrationIP($result['registration_ip']);
return $entity;
}
Re: Сервисный слой, как правильно?
тогда я бы посоветовал развязать все это:
связанные сущности лучше получать не через другое репо, а тут же - мотивация: другое репо заточено под другой aggregate root и может сразу заполнять сущность связями, которые нужны в одном контексте, но не нужны в контексте первого репозитория. Если не понятно, поясню.
Код: Выделить всё
private function toEntity(array $columns)
{
return $this->userHydrator->hydrate($columns);
}
Re: Сервисный слой, как правильно?
Это всёzelenin писал(а):тогда я бы посоветовал развязать все это:связанные сущности лучше получать не через другое репо, а тут же - мотивация: другое репо заточено под другой aggregate root и может сразу заполнять сущность связями, которые нужны в одном контексте, но не нужны в контексте первого репозитория. Если не понятно, поясню.Код: Выделить всё
private function toEntity(array $columns) { return $this->userHydrator->hydrate($columns); }
Код: Выделить всё
$entity = new User();
$entity->setId($result['id']);
$entity->setFirstName($result['first_name']);
$entity->setLastName($result['last_name']);
$entity->setPatronymic($result['patronymic']);
$entity->setEmail($result['email']);
$entity->setPasswordHash($result['password_hash']);
$entity->setAbout($result['about']);
$entity->setRegistrationDate(new \DateTime($result['registration_date']));
$entity->setLastVisitDate(new \DateTime($result['last_visit_date']));
$entity->setLastVisitIP($result['last_visit_ip']);
$entity->setRegistrationIP($result['registration_ip']);
return $entity;
Да, не понятно) если везде использовать lazy load, то таких проблем же не будет возникать?
Re: Сервисный слой, как правильно?
да, ну заодно добавьте метод extract для обратной операции. плюс интерфейс можно сделать общим для всех сущностей.Melodic писал(а):Это всёzelenin писал(а):тогда я бы посоветовал развязать все это:связанные сущности лучше получать не через другое репо, а тут же - мотивация: другое репо заточено под другой aggregate root и может сразу заполнять сущность связями, которые нужны в одном контексте, но не нужны в контексте первого репозитория. Если не понятно, поясню.Код: Выделить всё
private function toEntity(array $columns) { return $this->userHydrator->hydrate($columns); }
перенести в UserHydratorInterface::hydrate($array)?Код: Выделить всё
$entity = new User(); $entity->setId($result['id']); $entity->setFirstName($result['first_name']); $entity->setLastName($result['last_name']); $entity->setPatronymic($result['patronymic']); $entity->setEmail($result['email']); $entity->setPasswordHash($result['password_hash']); $entity->setAbout($result['about']); $entity->setRegistrationDate(new \DateTime($result['registration_date'])); $entity->setLastVisitDate(new \DateTime($result['last_visit_date'])); $entity->setLastVisitIP($result['last_visit_ip']); $entity->setRegistrationIP($result['registration_ip']); return $entity;
ну так мы выяснили, что создать вкусно пахнущий lazy load нетривиальная задача. Ну и невсегда lazy load везде нужен по умолчанию.Melodic писал(а):Да, не понятно) если везде использовать lazy load, то таких проблем же не будет возникать?
Re: Сервисный слой, как правильно?
В чём проблема не использовать final?zelenin писал(а):да, ну заодно добавьте метод extract для обратной операции. плюс интерфейс можно сделать общим для всех сущностей.Melodic писал(а):Это всёzelenin писал(а):тогда я бы посоветовал развязать все это:связанные сущности лучше получать не через другое репо, а тут же - мотивация: другое репо заточено под другой aggregate root и может сразу заполнять сущность связями, которые нужны в одном контексте, но не нужны в контексте первого репозитория. Если не понятно, поясню.Код: Выделить всё
private function toEntity(array $columns) { return $this->userHydrator->hydrate($columns); }
перенести в UserHydratorInterface::hydrate($array)?Код: Выделить всё
$entity = new User(); $entity->setId($result['id']); $entity->setFirstName($result['first_name']); $entity->setLastName($result['last_name']); $entity->setPatronymic($result['patronymic']); $entity->setEmail($result['email']); $entity->setPasswordHash($result['password_hash']); $entity->setAbout($result['about']); $entity->setRegistrationDate(new \DateTime($result['registration_date'])); $entity->setLastVisitDate(new \DateTime($result['last_visit_date'])); $entity->setLastVisitIP($result['last_visit_ip']); $entity->setRegistrationIP($result['registration_ip']); return $entity;
ну так мы выяснили, что создать вкусно пахнущий lazy load нетривиальная задача. Ну и невсегда lazy load везде нужен по умолчанию.Melodic писал(а):Да, не понятно) если везде использовать lazy load, то таких проблем же не будет возникать?
Re: Сервисный слой, как правильно?
слоеная архитектура подразумевает независимые слои. Не использовать final, означает протекание одного слоя в другой. Нужно найти решение, которое позволит во всех кейсах сделать рабочий lazy. Мои сущности - финальны. Я их помечаю final. То, что другой слой решает lazy через проксирование - это проблема другого слоя.Melodic писал(а):В чём проблема не использовать final?
Re: Сервисный слой, как правильно?
Ну и как тогда быть? Делать через $entityRepository->resolve($strategies); ?zelenin писал(а):слоеная архитектура подразумевает независимые слои. Не использовать final, означает протекание одного слоя в другой. Нужно найти решение, которое позволит во всех кейсах сделать рабочий lazy. Мои сущности - финальны. Я их помечаю final. То, что другой слой решает lazy через проксирование - это проблема другого слоя.Melodic писал(а):В чём проблема не использовать final?
Re: Сервисный слой, как правильно?
ничего другого придумать не могу. гугл тоже больше ничего не предлагает.Melodic писал(а):Ну и как тогда быть? Делать через $entityRepository->resolve($strategies); ?zelenin писал(а):слоеная архитектура подразумевает независимые слои. Не использовать final, означает протекание одного слоя в другой. Нужно найти решение, которое позволит во всех кейсах сделать рабочий lazy. Мои сущности - финальны. Я их помечаю final. То, что другой слой решает lazy через проксирование - это проблема другого слоя.Melodic писал(а):В чём проблема не использовать final?
DomainDependencyResolver::resolve
Re: Сервисный слой, как правильно?
Потребность в связях и lazy load обычно возникает при первой же попытке отображать на страницах сами сущности домена. Проблема полностью исчезает, если для листингов и вывода использовать не сами сущности, а отдельные DTO с нужными для каждого листинга наборами полей. В итоге в Repository можно оставить только методы find(), add(), save() и remove(), нужные для непосредственной работы с сущностями, а все остальные для выборок с критериями, паджинациями и сортировками переместить в отдельный ReadRepository.Melodic писал(а):Ну и как тогда быть?