интересный вопрос. Думаю, последний сервисный слой - хэндлеры, - как непосредственные исполнители приложения. Но на самом деле тут важен единый подход. Хэндлеры везде обрабатывают реквесты, а сервисы не в каждом хэндлере будут, поэтому надо выбрать тот слой, который будет везде.Melodic писал(а):Ещё вопрос есть команда логина LoginCommand.
Есть событие LoginEvent, которое выбрасывается после успешного логина. Так вот, кто должен его бросать? Хендлер LoginHandler или сервис authService (который отвечает за вход и его метод login() вызывается в хендлере)?
Сервисный слой, как правильно?
Re: Сервисный слой, как правильно?
Re: Сервисный слой, как правильно?
Т.е. будет везде?)zelenin писал(а): а сервисы не в каждом хэндлере будут, поэтому надо выбрать тот слой, который будет везде.
Ещё вопрос по QueryBus. Она же должна возвращать не саму модель,а её DTO?
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Сервисный слой, как правильно?
Разве тут вообще создание модели? Или изменение ее состояния?zelenin писал(а):во-первых, перекладываем ответственность за создание модели на фабрику
Какова ответственность сервиса "UserServiceInterface" или хендлера?
Как решить что пора остановится и количество слоев уже достаточно?
Можно ли убрать несоответствие семантики путем переименования метода? Вместо создания дополнительной фабрики?zelenin писал(а):во-вторых, убираем несоответствие в семантике типа changeRole, где на самом деле не смена, а установка роли происходит
Разве в фабрике не будет вызван метод changeRole с неверной семантикой? В чем тогда выгода переноса кода из сервиса в новый сервис (чем фабрика является)?
Жду Yii 3!
Re: Сервисный слой, как правильно?
Суть перехода к CommandBus в том, что мы код методов бывшего сервиса приложения:Melodic писал(а):Может ли хэндлер напрямую работать с моделью или хенедлер должен вызывать какие то методы сервисов?
Код: Выделить всё
class UserService
{
public function signUp() { ... }
public function updateProfile() { ... }
public function changeRole() { ... }
}
Код: Выделить всё
class UserSignUpHandler
{
public function handle() { ... }
}
class UserProfileUpdateHandler
{
public function handle() { ... }
}
class UserRoleChangeHandler
{
public function handle() { ... }
}
Так что как работали с моделями в UserService, так и работаем в хэндлерах.
Re: Сервисный слой, как правильно?
Есть команды регистрации и логина. Логин соответственно логинит, а регистрация и регистрирует и логинит после успешного создания пользователя. Как быть в таком случае, т.к. получается дублирование , если метод логина не вынести в сервис?ElisDN писал(а):Суть перехода к CommandBus в том, что мы код методов бывшего сервиса приложения:Melodic писал(а):Может ли хэндлер напрямую работать с моделью или хенедлер должен вызывать какие то методы сервисов?
разносим по классам:Код: Выделить всё
class UserService { public function signUp() { ... } public function updateProfile() { ... } public function changeRole() { ... } }
А оригинальный UserService удаляем.Код: Выделить всё
class UserSignUpHandler { public function handle() { ... } } class UserProfileUpdateHandler { public function handle() { ... } } class UserRoleChangeHandler { public function handle() { ... } }
Так что как работали с моделями в UserService, так и работаем в хэндлерах.
Re: Сервисный слой, как правильно?
Логин:Melodic писал(а):Есть команды регистрации и логина. Логин соответственно логинит, а регистрация и регистрирует и логинит после успешного создания пользователя. Как быть в таком случае, т.к. получается дублирование , если метод логина не вынести в сервис?
Код: Выделить всё
public function handle(UserLoginCommand $command)
{
$user = $this->userRepository->findByLogin($command->login);
$this->authManager->login($user);
}
Код: Выделить всё
public function handle(UserSignUpCommand $command)
{
$user = User::signup($command->login, $command->password, $command->role);
$this->userRepository->save($user);
$this->authManager->login($user);
}
Re: Сервисный слой, как правильно?
ну у вас прилетает реквест от клиента (форма, api, консоль) - вы реквест куда-то передаете для обработки. Если вы решили передавать в шину команд, сделав из нее сервисный слой, значит вся соль приложения в этом слое - пусть он и генерит все события.Melodic писал(а):Т.е. будет везде?)zelenin писал(а): а сервисы не в каждом хэндлере будут, поэтому надо выбрать тот слой, который будет везде.
шина возвращает модели, отдаете в респонсе дто.Melodic писал(а):Ещё вопрос по QueryBus. Она же должна возвращать не саму модель,а её DTO?
Код: Выделить всё
$models = $queryBus->execute(new AllPosts);
$dtos = $postCollectionAssembler->toDto($models);
Re: Сервисный слой, как правильно?
У меня вопросец. А если я в сервисе должен сохранить модель, у которой должен быть userId. Хочу понять, как делать правильно?
С одной стороны у меня есть форма с полями, принимающая данные от пользователя или из другого источника, с другой стороны сервис. Дублировать наборы полей везде не хотелось бы.
Код: Выделить всё
class SubscriptionForm extends \yii\base\Model
{
private $user;
public function __construct(IdentityInterface $user, $config = [])
{
$this->user = $user;
parent::__construct($config);
}
// rules and other methods...
public function save()
{
$model = new Subscription();
$model->userId = $this->user->id;
$service = new SubscriptionService($model);
$service->create();
}
}
class SubscriptionService
{
private $subscription;
public function __construct(\common\models\Subscription $model)
{
$this->subscription = $model;
}
public function create()
{
if (!$this->subscription->isNewRecord()) {
throw new InvalidConfigException('Unable to create exist model.');
}
return $this->subscription->save(false);
}
}
Re: Сервисный слой, как правильно?
а что тут как не создание модели?slavcodev писал(а):Разве тут вообще создание модели? Или изменение ее состояния?
начнем с того, что в UL нет понятия "создание класса User". Есть "регистрация юзера", "генерация тестового юзера", "создание юзера через апи"
Допустим, в нашем доменном слое существуют доменные модели (допущение лишь для понимания семантики). Если мы не создаем модель через фабрику, то любое инстанциировнаие черех конструктор нам будет генерить событие UserCreated/UserRegistered, в том числе и создание объекта из данных базы ($userRepo->getById($id)). Решение: создавать объект в фабричных методах, каждый из которых будет генерить свое событие UserRegistered/UserCreatedFromApi/RandomUserGenerated (а инстанциирование в репозитории вообще не будет генерить).
Смотрим дальше: есть бизнес-операция - смена роли из админки. Меняем так:
Код: Выделить всё
$user->changeRole($role);
Должна ли регистрация генерить 4 события UserRegistered/RoleChanged/PasswordSetted/LoginSetted? Я считаю, что регистрация - операция атомарная, и событие должно быть одно. Соответственно использование метода changeRole в данном случае неприемлемо. Варианты решения есть - по вкусу разработчика.
инкапсулировать всю сопутствующую логику - создание юзера (через фабрику), отсылку писем, логгирование итд.slavcodev писал(а):Какова ответственность сервиса "UserServiceInterface" или хендлера?
Количество слоев не меняется. Речь об одной ответственности на класс. Создание модели может происходить разными способами: через заполнение формы регистрации с 10 полями, реквестом в апи от партнера с тремя полями, генерацией рандомного юзера админом нажатием кнопки Сгенерировать. Три способа создания юзера -> три разных реквеста, три метода в UserFactory (или named constructors - статические фабричные методы), обрабатывающих каждый тип реквеста. А сервис кроме того, что через фабрику создает юзера, еще кидает эвенты в шину, логирует, шлет письма админам, кидает денормализованную модель в elasticsearch.slavcodev писал(а):Как решить что пора остановится и количество слоев уже достаточно?
семантика именно о соответствии именования смыслу.slavcodev писал(а):Можно ли убрать несоответствие семантики путем переименования метода? Вместо создания дополнительной фабрики?zelenin писал(а):во-вторых, убираем несоответствие в семантике типа changeRole, где на самом деле не смена, а установка роли происходит
выше ответилslavcodev писал(а):Разве в фабрике не будет вызван метод changeRole с неверной семантикой?
фабрика не является сервисом. Фабрика - класс, знающий о том, как создавать другой класс, полезный при наличии нескольких способов инстанциирования.slavcodev писал(а):В чем тогда выгода переноса кода из сервиса в новый сервис (чем фабрика является)?
Re: Сервисный слой, как правильно?
непонятно - что где дублировать?SiZE писал(а):У меня вопросец. А если я в сервисе должен сохранить модель, у которой должен быть userId. Хочу понять, как делать правильно?
С одной стороны у меня есть форма с полями, принимающая данные от пользователя или из другого источника, с другой стороны сервис. Дублировать наборы полей везде не хотелось бы.Код: Выделить всё
class SubscriptionForm extends \yii\base\Model { private $user; public function __construct(IdentityInterface $user, $config = []) { $this->user = $user; parent::__construct($config); } // rules and other methods... public function save() { $model = new Subscription(); $model->userId = $this->user->id; $service = new SubscriptionService($model); $service->create(); } } class SubscriptionService { private $subscription; public function __construct(\common\models\Subscription $model) { $this->subscription = model$model; } public function create() { if (!$this->subscription->isNewRecord()) { throw new InvalidConfigException('Unable to create exist model.'); } return $this->subscription->save(false); } }
у вас все неверно. точнее save в форме непонятно зачем.
Код: Выделить всё
$form->load(...);
if(!$form->validate()) {
// обрабатываем ошибки
}
$subscriptionService->createFromForm(SubscriptionForm $form);
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Сервисный слой, как правильно?
Вот и я про это. Регистрация пользователя - это процесс изменения модели User в нужное состояние.zelenin писал(а):а что тут как не создание модели?
начнем с того, что в UL нет понятия "создание класса User". Есть "регистрация юзера", "генерация тестового юзера", "создание юзера через апи"
Фабрики используются для сложного создании объекта (сложных конструктор: VO, и т.д. в нем)
Это если не правильно использовать конструктор. Конструктор это всего лишь инициализирование модели, никаких событий оно не генерит.zelenin писал(а):то любое инстанциировнаие черех конструктор нам будет генерить событие UserCreated/UserRegistered
Фабрики - создание объектов. Никаких событий не должны генерить. По хорошему их генерят рич-модели, на крайний случай сервисы (хотя это нарушает инкапсуляцию модели)zelenin писал(а):Решение: создавать объект в фабричных методах, каждый из которых будет генерить свое событие UserRegistered/UserCreatedFromApi/RandomUserGenerated (а инстанциирование в репозитории вообще не будет генерить).
Вот поэтому я рекомендую инкапсулировать все в рич-моделиzelenin писал(а):Смотрим дальше: есть бизнес-операция - смена роли из админки. Меняем так:Вызов метода генерит событие RoleChanged.Код: Выделить всё
$user->changeRole($role);
Должна ли регистрация генерить 4 события UserRegistered/RoleChanged/PasswordSetted/LoginSetted? Я считаю, что регистрация - операция атомарная, и событие должно быть одно. Соответственно использование метода changeRole в данном случае неприемлемо. Варианты решения есть - по вкусу разработчика.
Регистрация и соотв. события
Код: Выделить всё
$user->register(...);
Код: Выделить всё
$user->changeRole($role);
Тут все зависит от бизнеса.zelenin писал(а):Я считаю, что регистрация - операция атомарная, и событие должно быть одно.
Доменное событие - событие произошедшее с агрегейтом важное для бизнеса.
Т.е. вполне возможно что для бизнеса важно записать не одно событие при регистрации.
А хендлер для команды тогда чем занимается если не тем же что ты описал для сервиса?zelenin писал(а):инкапсулировать всю сопутствующую логику - создание юзера (через фабрику), отсылку писем, логгирование итд.slavcodev писал(а):Какова ответственность сервиса "UserServiceInterface" или хендлера?
Так и почему это не может сделать эти три хендлеры? Каждый свою логику содержит, свою ответственность? Зачем новый слой - сервисы?zelenin писал(а):Три способа создания юзера -> три разных реквеста, три метода в UserFactory (или named constructors - статические фабричные методы), обрабатывающих каждый тип реквеста. А сервис кроме того, что через фабрику создает юзера, еще кидает эвенты в шину, логирует, шлет письма админам, кидает денормализованную модель в elasticsearch.
ОК. Тут дело в том что "сервисы" понятие используемое в разных контекстах. Есть "Сервисы" - юз кейсы", про то что ты говоришь, и "сервисы" в контексте деления классов на два типа "Data-objects" и "Services objects", эти я имел виду, фабрика это объект-сервис.zelenin писал(а):фабрика не является сервисом. Фабрика - класс, знающий о том, как создавать другой класс, полезный при наличии нескольких способов инстанциирования.
Жду Yii 3!
Re: Сервисный слой, как правильно?
а как надо изменить объект, чтобы зарегистрировать юзера?slavcodev писал(а):Вот и я про это. Регистрация пользователя - это процесс изменения модели User в нужное состояние.
фабрики используются для создания объекта (я кстати указал варианты использования фабрик - разные методы на разные реквесты)slavcodev писал(а):Фабрики используются для сложного создании объекта (сложных конструктор: VO, и т.д. в нем)
а как надо правильно?slavcodev писал(а):Это если не правильно использовать конструктор.zelenin писал(а):то любое инстанциировнаие черех конструктор нам будет генерить событие UserCreated/UserRegistered
а что тогда генерит?slavcodev писал(а):Конструктор это всего лишь инициализирование модели, никаких событий оно не генерит.
модель и будет генеритьslavcodev писал(а):Фабрики - создание объектов. Никаких событий не должны генерить. По хорошему их генерят рич-модели, на крайний случай сервисы (хотя это нарушает инкапсуляцию модели)
Код: Выделить всё
$model->raise(new UserRegistered);
да все верно. Но регистрация юзера - это именно создание объекта.slavcodev писал(а):Вот поэтому я рекомендую инкапсулировать все в рич-моделиzelenin писал(а):Смотрим дальше: есть бизнес-операция - смена роли из админки. Меняем так:Вызов метода генерит событие RoleChanged.Код: Выделить всё
$user->changeRole($role);
Должна ли регистрация генерить 4 события UserRegistered/RoleChanged/PasswordSetted/LoginSetted? Я считаю, что регистрация - операция атомарная, и событие должно быть одно. Соответственно использование метода changeRole в данном случае неприемлемо. Варианты решения есть - по вкусу разработчика.
что тут метод register делает? зачем он на готовом объекте? мне кажется ты пошел другим путем - регистрация после создания объекта, а не создание объекта как регистрация. Интересно, но распиши тогда метод register.slavcodev писал(а):Регистрация и соотв. событияКод: Выделить всё
$user->register(...);
да ради бога. Если для целей бизнеса нужно чтобы кроме UserChangedFromApi генерилось RoleChanged, сделаем. Но мы не про цели бизнеса, а про конкретное бизнес-событие - "юзер зарегистрирован". Без доп. запросов от бизнеса событие одно. Не будешь же ты на все кейсы генерить все возможные события.slavcodev писал(а):В обоих поведениях модели будет использоваться (приватный метод setRole)Тут все зависит от бизнеса.zelenin писал(а):Я считаю, что регистрация - операция атомарная, и событие должно быть одно.
Доменное событие - событие произошедшее с агрегейтом важное для бизнеса.
Т.е. вполне возможно что для бизнеса важно записать не одно событие при регистрации.
хэндлер команды и есть сервис. Ты либо используешь сервисный слой либо шину команд.slavcodev писал(а):А хендлер для команды тогда чем занимается если не тем же что ты описал для сервиса?zelenin писал(а):инкапсулировать всю сопутствующую логику - создание юзера (через фабрику), отсылку писем, логгирование итд.slavcodev писал(а):Какова ответственность сервиса "UserServiceInterface" или хендлера?
и тут я не понял о чем ты. Я нигде не писал про параллельно существующие хэндлеры и сервисы. Сервисы могут использоваться внутри хэндлеров, но уже никак сервисы сервисного слоя, а как сервис какой-то сторонней сущности - сервис авторизации от сторонней либы, сервис-клиент апи итд. Сервис - это всего лишь служба, а не обязательно часть сервисного слоя.slavcodev писал(а):Так и почему это не может сделать эти три хендлеры? Каждый свою логику содержит, свою ответственность? Зачем новый слой - сервисы?zelenin писал(а):Три способа создания юзера -> три разных реквеста, три метода в UserFactory (или named constructors - статические фабричные методы), обрабатывающих каждый тип реквеста. А сервис кроме того, что через фабрику создает юзера, еще кидает эвенты в шину, логирует, шлет письма админам, кидает денормализованную модель в elasticsearch.
ну вот, о чем я выше написал. Сервис - это практически все что угодно, выполняющее какую-либо служебную задачу. Условно и фабрику можно назвать сервисом, но обычно фабрика есть фабрика.slavcodev писал(а):ОК. Тут дело в том что "сервисы" понятие используемое в разных контекстах. Есть "Сервисы" - юз кейсы", про то что ты говоришь, и "сервисы" в контексте деления классов на два типа "Data-objects" и "Services objects", эти я имел виду, фабрика это объект-сервис.zelenin писал(а):фабрика не является сервисом. Фабрика - класс, знающий о том, как создавать другой класс, полезный при наличии нескольких способов инстанциирования.
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Сервисный слой, как правильно?
Это доменный эксперт может рассказать. Предполагаю что нужно ввести: имя и другие поля, установить в модели время регистрации, бросить события нужные.zelenin писал(а):а как надо изменить объект, чтобы зарегистрировать юзера?
zelenin писал(а):фабрики используются для создания объекта (я кстати указал варианты использования фабрик - разные методы на разные реквесты)
Я считаю это не варианты использования фабрик, все это сценарии использования (Use cases), разные сервисы,zelenin писал(а):Есть "регистрация юзера", "генерация тестового юзера", "создание юзера через апи"
которые могут вызывать один и больше поведения модели, тем самым изменяя ее состоянии.
Но вызывать поведения уже созданной модели.
zelenin писал(а):а как надо правильно?
Хочу заметить что когда я говорю "правильно", это мое личное мнение, полученное моим персональным опытом работы.zelenin писал(а):а что тогда генерит?
Я не кого не пытаюсь научить, и не пытаюсь быть умнее остальных.
Я считаю, что конструктор, это всего лишь элемент ООП, инициализирование модели в ее начально но валидное состояние. Считая черновик, полотно для последующих изменений.
На примере пользователя и тех трех юз кейсов, предположу что модель имеет следующие свойства, конструктор и поведения (с пояснениями в комментах)
Код: Выделить всё
class User
{
private $email;
private $name;
private $password;
private $createdTime;
private $registeredTime;
private $status;
private $confirmationToken;
private $role;
/**
* Чтоб инициализировать модель в ее начальное валидное состояние, конструктор должен:
* - Требовать значение умолчания которых нельзя выдумать
* - Установить свойствам модели умолчания
*
* Любая сущность имеет идентификатор (ИД) отличающий ее от других.
* Идентификатор это не обязательно UUID, или инкрементное число, это уникальное значение в контексте этой модели.
* Я допускаю что наш домен требует емайл пользователя, и он достаточно уникален, это и будет ИД сущности,
* его мы запрашиваем. Начальный статус "inactive". Для остальных свойств "null" является валидным значением.
*
* Никаких событий конструктор не бросает, т.к. для домена никакого интереса это не вызывает.
*/
function __constructor($email)
{
$this->email = $email;
$this->status = 'inactive';
$this->role = 'User';
}
/**
* Поведение пользователя, переход в состояние тестового пользователя.
*
* По условиям домена пароль может придумывает админ, или каким-то сервисом генерится.
* Токен генерится стороним сервисом.
*
* Устанавливаем нужный статус и время создания пользователя (не путать со временем создания объекта)
*/
function draft($password, $confirmationToken)
{
if ($this->createdTime !== null) {
throw new LogicException('Лажа в архитектуре, попытка создать тестового пользователя второй раз');
}
$this->password = $password;
$this->confirmationToken = $confirmationToken;
$this->createdTime = new DateTime();
$this->status = 'pending-confirmation';
$this->record(new TestUserWasCreatedEvent());
}
/**
* Поведение пользователя, переход в состояние только что зарегистрированного пользователя.
*
* Требуем необходимые от пользователя данные.
* Устанавливаем нужный статус, время создания пользователя (не путать со временем создания объекта)
* и время регистрации (допуская что это требование домена, может и не быть)
*/
function register($name, $password, $confirmationToken)
{
if ($this->createdTime !== null) {
throw new LogicException('Лажа в архитектуре, попытка зарегистрировать пользователя второй раз');
}
$this->name = $name;
$this->password = $password;
$this->confirmationToken = $confirmationToken;
$this->createdTime = new DateTime();
$this->registerTime = new DateTime();
$this->status = 'pending-confirmation';
$this->record(new NewUserWasRegisteredEvent());
}
/**
* Поведение пользователя, активация (подтверждение).
*/
function activate($confirmationToken)
{
if ($this->status === 'active') {
throw new LogicException('Лажа в архитектуре, попытка активировать пользователя второй раз');
}
if ($this->confirmationToken !== $confirmationToken) {
throw new UnexpectedValueException('Ай-ай-ай, код не подходит');
}
// при условии что домен ставит такое требование, др. вариант просто установить время регистрации в момент активации.
if ($this->registerTime === null) {
throw new DomainException('Пользователь с тестовым паролем и без имени, нужно перенаправить и запросить новый');
}
$this->status = 'active';
$this->record(new UserWasActivatedEvent());
}
/**
* Поведение пользователя, активация (подтверждение) тестового пользователя,
* с обязательной сменой пароля и имени по умолчанию.
*
* Здесь два события будут брошены, т.к. допускаем что домену очень важно знать и среагировать на оба.
*/
function acceptInvitation($confirmationToken, $name, $password)
{
if ($this->status === 'active') {
throw new LogicException('Лажа в архитектуре, попытка активировать пользователя второй раз');
}
if ($this->confirmationToken !== $confirmationToken) {
throw new UnexpectedValueException('Ай-ай-ай, код не подходит');
}
$this->name = $name;
$this->password = $password;
$this->registerTime = new DateTime();
$this->status = 'active';
$this->record(new UserInvitationWasAcceptedEvent());
$this->record(new UserWasActivatedEvent());
}
/**
*/
function changeRole(User $admin, $role)
{
if ($admin->role !== 'Admin') {
throw new LogicException('Лажа в архитектуре, попытка поменять роль, не имея нужных админских прав');
}
$this->role = $role;
$this->record(new UserRoleWasChangedEvent());
}
}
Дубликаты кода в некоторых методах, рефакторингом убираются в приватные методы модели.
Сервисы для юз кейсов (при помощи хендлеров для команд, хотя и без них если не хочется CQRS)
Код: Выделить всё
class CreateTestUserHandler
{
/**
* Емайл вводит админ в админке.
*
* passwordGenerator, tokenGenerator, userRepository - сервисы зависимости хендлера, устанавливаются в конструтокре.
*/
function handle(CreateTestUserCommand $data)
{
$user = new User($data->email);
$user->draft($this->passwordGenerator->generate(), $this->tokenGenerator->generate());
$this->userRepository->save($user);
}
}
class RegisterUserHandler
{
/**
* Емайл, имя, пароль вводит пользователь в форме.
* Подтверждающий пароль упущен, я считаю это не имеет смысла для домена,
* а только часть UI, чтоб пользователь не ошибся, так что это оставляем на ответсвенность валидации команды.
*
* tokenGenerator, userRepository - сервисы зависимости хендлера, устанавливаются в конструтокре.
*/
function handle(RegisterUserCommand $data)
{
$user = new User($data->email);
$user->register(
$data->name,
$data->password,
$this->tokenGenerator->generate()
);
$this->userRepository->save($user);
}
}
class CreateUserApiHandler
{
/**
* Не могу придумать отличие от регистрации через форму.
* Для домена, для сущности пользователя, вроде как все равно из какого приложения регистрируется пользователь, хоть из консоли.
* От балды могу допустить что через апи можно сразу активировать указав token (или может другой атрибут)
*/
function handle(CreateUserApiCommand $data)
{
$user = new User($data->email);
$user->register(
$data->name,
$data->password,
$data->confirmationToken ?? $this->tokenGenerator->generate()
);
if ($data->confirmationToken !== null) {
$user->activate($data->confirmationToken);
}
$this->userRepository->save($user);
}
}
class ActivateUserHandler
{
/**
* 1. getByEmail - всегда возвращает сущность или бросает исключение
* 2. Ловить исключения тут можно если нужно, но это может делать контролер,
* который ответит либо 404 либо перенаправит на страницу где нужно ввести имя и пароль для тестового пользователя
*/
function handle(ActivateUserCommand $data)
{
$user = $this->userRepository->getByEmail($data->email);
$user->activate($data->confirmationToken);
$this->userRepository->save($user);
}
}
class ActivateTestUserHandler
{
/**
*/
function handle(ActivateTestUserCommand $data)
{
$user = $this->userRepository->getByEmail($data->email);
$user->activate($data->confirmationToken, $data->name, $data->password);
$this->userRepository->save($user);
}
}
class UserController
{
function activateAction()
{
$email = $this->httpRequest->getQuery('email');
$token = $this->httpRequest->getQuery('token');
try {
$this->commandBus->dispatch(new ActivateUserCommand($email, $token))
} catch (OutOfRangeException $e) {
return $this->notFound('Пользователь не найден');
} catch (DomainException $e) {
return $this->redirect('activateTestUser', compact('email', 'token'));
}
return $this->redirect('login');
}
function activateTestUserAction()
{
$email = $this->httpRequest->getQuery('email');
$token = $this->httpRequest->getQuery('token');
$name = $this->httpRequest->getPost('name');
$password = $this->httpRequest->getPost('password');
try {
$this->commandBus->dispatch(new ActivateTestUserCommand($email, $token, $name, $password))
} catch (OutOfRangeException $e) {
return $this->notFound('Пользователь не найден');
}
return $this->redirect('login');
}
}
class UserRepository
{
function save(User $user)
{
// сохраняем пользователя в базу данных (имплементация упущена)
// после чего обрабатываем события произошедшие с ним,
// отсылаем емитеру чтоб оповестил подписчиков из других контектов
foreach ($user->flushEvents() as $event) {
$this->eventEmitter->emit($event);
}
}
}
Не понял что ты имел виду, по коду который ты привел, событие генерит сервис (фабрика) и передает в модель.zelenin писал(а):модель и будет генеритьтолько в зависимости от фабрики разные события будут.Код: Выделить всё
$model->raise(new UserRegistered);
Это нарушает инкапсуляцию модели, можно забыть ошибиться с событием в общем на лажать.
Да и ненужно фабрике или другим сервисам знать о бизнес правилах домена, частью чего являются события.
Да, я не правильно растолковал твои сообщения, перечитал, понял свою ошибку и верно.zelenin писал(а):и тут я не понял о чем ты. Я нигде не писал про параллельно существующие хэндлеры и сервисы
Угу, только иницилизирование сущности по сути два: новая сущность не существующая в базе в состоянии начальном,zelenin писал(а):Фабрика - класс, знающий о том, как создавать другой класс, полезный при наличии нескольких способов инстанциирования.
и сущность восстановленная из хранилища, в состоянии сохраненным. Вот это я называю "инстанциирования".
Все остальное: регистарция, активация, изменение роли, пароля, это поведения объекта, изменяющее его состояние.
Жду Yii 3!
Re: Сервисный слой, как правильно?
вообще в целом мне понравился твой подход, но он не традиционен. Ты по сути в конструктор подаешь только идентификатор, а три предложенных мною статических фабричных метода (так называемые именованные конструкторы (с) Верраес) переносишь в поведенческие методы объекта, делая more ddd. Пожалуй, возьму на вооружение.
Резюмирую: создание объекта на основе одного id - вещь нетрадиционная и мной ни разу не встречаемая у известных авторов, но предложенное тобой решение, имхо, красивое с т.з. ddd.
Давай не будем фабрику называть сервисом, чтобы не путать с сервисами сервисного слоя и инфраструктурными сервисами. Фабрика - это фабрика.Не понял что ты имел виду, по коду который ты привел, событие генерит сервис (фабрика) и передает в модель.zelenin писал(а):модель и будет генеритьтолько в зависимости от фабрики разные события будут.Код: Выделить всё
$model->raise(new UserRegistered);
поэтому мы создаем одну точку входа - UserFactory::registerUseCase(..)Это нарушает инкапсуляцию модели, можно забыть ошибиться с событием в общем на лажать.
фабрика - часть слоя, в которому создает объект - домена. Но в целом ты прав - ей не следует знать о событии. То, что предложил я, был вариант обойти то, что модель может быть создана несколькими способами, должна выкинуть разные события, а конструктор у нас один. Но твой вариант с $user->register(..) мне понравился.Да и ненужно фабрике или другим сервисам знать о бизнес правилах домена, частью чего являются события.
Резюмирую: создание объекта на основе одного id - вещь нетрадиционная и мной ни разу не встречаемая у известных авторов, но предложенное тобой решение, имхо, красивое с т.з. ddd.
Re: Сервисный слой, как правильно?
А модель Subscription в сервис как прокинуть? И может ли сервис работать с несколькими наследниками модели Subscription? И где присваивать userId? =)zelenin писал(а):Код: Выделить всё
$subscriptionService->createFromForm(SubscriptionForm $form);
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Сервисный слой, как правильно?
Я сразу понял про что ты, я сам читаю статьи Mathias.zelenin писал(а):статических фабричных метода (так называемые именованные конструкторы (с) Верраес)
Но к этому подходу отношусь неуверенно. Я считаю что оно нарушает принципы ООП, а именно инкпасуляцию,
из-за того что устанавливаются приватные свойства.
До того момента когда он делал конструтор приватный и без параметров.
ИМХО это просто более современный хак подхода с рефлексией, используемый некоторыми ОРМ.
Фабрика это один из типов порождающих паттернов. Есть и другие варианты.zelenin писал(а):Давай не будем фабрику называть сервисом, чтобы не путать с сервисами сервисного слоя и инфраструктурными сервисами. Фабрика - это фабрика.
Применимые для проектирования сервисов создающих объекты.
Поэтому я все же рекомендую называть их сервисами, это как мне кажется наоборот ведет к более четкому понимаю вещей.
О том что термин сервисы используется часто и в разных контекстах в программировании.
По сути сервисы это объекты не имеющие состояния, чья ответственность манипулировать данными.
Другими словами один экзепляр должен уметь обработать несколько раз разные данные,
и на результат не должно влиять который раз по счету он используется.
Это я уточнил, т.к. сообщением чуть выше, кто-то писал сервис, передавай данный в конструтор.
А Infrastructure services, Application services, Domain services - все это уровни в которых используются эти объекты и какие данными обрабатывают.
Сервисы сервисного слоя - это совсем из другой оперы. Это элементы Union Architecture (слоистой архитектуры, что-то типа улучшение MVC), где между C и M, добавляется еще один слой. Эти сервисы по идее могут быть любого из тех трех типов что я выше написал.
Небольшое уточнение:zelenin писал(а):создание объекта на основе одного id - вещь нетрадиционная и мной ни разу не встречаемая у известных авторов
Не одного ИД, а на основе данных без которых объект не может принять свое начальное состояние.
В других сущностях это может быть не один ИД, даже с User это может быть, все зависит от домена.
В объектах сервисов, там в конструктор передаются то без чего сервис не может инициализоваться, все зависимости.
В случае с Value Object, т.к. это модель комплексного значения, состоящего из нескольких данных, скорее всего все они являются обязательными для инициализации и должны сразу устанавливаться в конструтор.
Жду Yii 3!
Re: Сервисный слой, как правильно?
Возможно ли это всё в какой то степени применить к ActiveRecord ,как к доменной модели?
Re: Сервисный слой, как правильно?
Ваш подход можно и со статическими конструкторами совместить:slavcodev писал(а):Я считаю, что конструктор, это всего лишь элемент ООП, инициализирование модели в ее начально но валидное состояние. Считая черновик, полотно для последующих изменений.
Код: Выделить всё
class User
{
...
function __constructor($email)
{
$this->email = $email;
$this->status = 'inactive';
$this->role = 'User';
}
static function draft($email, $password, $confirmationToken)
{
$user = new self($email);
$user->password = $password;
$user->confirmationToken = $confirmationToken;
$user->createdTime = new DateTime();
$user->status = 'pending-confirmation';
$user->recordEvent(new TestUserWasCreatedEvent($user));
return $user;
}
static function register($email, $name, $password, $confirmationToken)
{
$user = new self($email);
$user->name = $name;
$user->password = $password;
$user->confirmationToken = $confirmationToken;
$user->createdTime = new DateTime();
$user->registerTime = new DateTime();
$user->status = 'pending-confirmation';
$user->recordEvent(new NewUserWasRegisteredEvent($this));
return $user;
}
function activate($confirmationToken)
{
$this->guardIsActive();
$this->guardConfirmationTokenIsCorrect($confirmationToken);
$this->status = 'active';
$this->recordEvent(new UserWasActivatedEvent($this));
}
}
Код: Выделить всё
function handle(RegisterUserCommand $data)
{
$user = User::register($data->email, $data->name, $data->password, $this->tokenGenerator->generate());
$this->userRepository->save($user);
}
- slavcodev
- Сообщения: 3134
- Зарегистрирован: 2009.04.02, 21:42
- Откуда: Valencia
- Контактная информация:
Re: Сервисный слой, как правильно?
Возможно конечно, но я предпочитаю так не делать.ElisDN писал(а):Ваш подход можно и со статическими конструкторами совместить
Причину написал выше, лично я считаю это обходом инкапсуляции через установку значений приватным свойствам не через конструктор или поведения сущности.
Жду Yii 3!
Re: Сервисный слой, как правильно?
Возможно, только не сможете переписать оригинальный конструктор. И вложенные объекты вроде $company->address->name нужно будет реализовывать через связи hasOne.Melodic писал(а):Возможно ли это всё в какой то степени применить к ActiveRecord, как к доменной модели?