Сервисы и объекты реквестов (модели), как быть?

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
kolotek
Сообщения: 13
Зарегистрирован: 2017.06.27, 20:16

Сервисы и объекты реквестов (модели), как быть?

Сообщение kolotek »

Здравствуйте. Недавно прочел статью ElisDN про композитные формы. И вдохновился на разделение AR модели и хранения данных от реквеста в ней.
Но вот недавно начал применять, на практике нечто подобное, и, как всегда это бывает, начали появляться подводные камни.

Например ситуация (выдуманная но хорошо описывающая проблему), есть у нас такие участники:

1. Два actions
- update-post на который приходят title и text поста
- quick-update-post на который приходит только title
2. Интерфейс:

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

interface UpdatePostRequestInterface
{
	public function getTitle();

	public function getText();
}
3. Модель-форма

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

class UpdatePostRequest extends Model implements UpdatePostRequestInterface 
{
        public function getTitle(){
        	return $this->title;
        }
    
    public function getText(){
    
            return $this->text;
    
    }
}
4. Сервис 


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

class UpdatePostService
{
	public function execute($post, UpdatePostRequestInterface $request)
	{
		$post->title = $request->getTitle();
		$post->text = $request->getText();
    
	}
}

Вроде все работает нормально. Но если изменить пост через action quick-update-post, то текст поста будет затираться т.к. на quick-update-post не приходит $_POST[‘text’], а приходит только $_POST[‘title’].



Какие-то костыльные решения, могу придумать. Например добавить в класс метод fillDefault($post), или передавать $post в конструктор UpdatePostRequest и затем заполнять поля модели. Но такое решение не очень нравится.

Подскажите, пожалуйста, как более правильно разрулить эту ситуация? Может слишком перемудрил с этими объектами реквестов?
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение samdark »

При значении null не затирать.
kolotek
Сообщения: 13
Зарегистрирован: 2017.06.27, 20:16

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение kolotek »

Будет не совсем удобно, скорее всего. Это только в примере два поля. В реальном проекте может быть до 10 и больше.
Начнется такое счастье...

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

if($request->getTitle()!==null){
	$post->title=$request->getTitle();
}

if($request->getText()!==null){
	$post->text=$request->getText();
}

if($request->getCategory()!==null){
	$post->category=$request->getCategory()->id;
}

...etc
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение noLogicOnlyWar »

Создайте 2 формы, у вас же по описанию их 2. 2 экшена = 2 юз-кейса = 2 сервиса = 2 формы (вернее 2 команды но у вас видимо команда = форма). Интерфейс для команды помойму явно излишество.
Аватара пользователя
Йож
Сообщения: 574
Зарегистрирован: 2015.08.26, 03:05

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение Йож »

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

Ну и валидации у Вас никакой не вижу.
kolotek
Сообщения: 13
Зарегистрирован: 2017.06.27, 20:16

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение kolotek »

noLogicOnlyWar писал(а): 2017.12.01, 00:15 Создайте 2 формы, у вас же по описанию их 2. 2 экшена = 2 юз-кейса = 2 сервиса = 2 формы (вернее 2 команды но у вас видимо команда = форма). Интерфейс для команды помойму явно излишество.
ну не знаю, как мне кажется, тут операция одна, соответственно и сервис должен быть один. По идее мы же сохраняем пост и там, и там. Если дублировать сервис, как по мне не очень хорошая идея т.к. там же может быть разная другая логика.
Например
  • пересчитать каунтер для постов категории
  • изменить дату активности автора
  • открыть категорию, если это был первый пост в ней
Если будет два сервиса, все эти действия будут дублироваться.

Насчет интерфейса для реквеста. Почему считаете излишним? Как по мне это очень удобно. Например у меня есть две версии API и названия полей в них отличаются. Соответственно, если есть интерфейс, то я не завязываюсь на какой-то конкретной реализации реквеста, мне просто нужно, чтобы он отдавал необходимые данные по запросу, а как он их, где и из каких полей он достает эти данные - это не забота сервиса. Сервис знает только об интерфейсе.

Йож писал(а): 2017.12.01, 05:05 В UpdatePostService передавать сценарий, и там уже разруливать, какие данные обновлять по тому или иному сценарию. Также в будущем это поможет добавлять при желании новые фишки для сценариев.

Ну и валидации у Вас никакой не вижу.
Не знаю, если с учетом сервисов и реквестов, еще передавать сценарий, это уже откровенно выглядит как какой-то костыль, не?
У меня нет валидации т.к. это псевдокод. Я просто хочу понять как поступать в подобных ситуациях с точки зрения проектирования и разработки с использованием сервисного слоя.
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение ElisDN »

kolotek писал(а): 2017.11.30, 21:33 Например добавить в класс метод fillDefault($post), или передавать $post в конструктор UpdatePostRequest и затем заполнять поля модели. Но такое решение не очень нравится.
Так и сделайте, как в примерах с формами в http://www.elisdn.ru/blog/111/yii2-composite-forms
kolotek
Сообщения: 13
Зарегистрирован: 2017.06.27, 20:16

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение kolotek »

ElisDN писал(а): 2017.12.01, 10:02
kolotek писал(а): 2017.11.30, 21:33 Например добавить в класс метод fillDefault($post), или передавать $post в конструктор UpdatePostRequest и затем заполнять поля модели. Но такое решение не очень нравится.
Так и сделайте, как в примерах с формами в http://www.elisdn.ru/blog/111/yii2-composite-forms
Спасибо. Наверное так и поступлю. Какие-то общие недочеты в подобном подходе (сервисыа-реквесты) не видите? Используете такой подход повсеместно, даже если операция не сложная? Что думаете насчет разделения на два сервиса, как предлагали выше?

И где, по вашему мнению, лучше выполнять подобные операции? В сервисе, или вешать это все дело на ивент?
kolotek писал(а): 2017.12.01, 09:59
  • пересчитать каунтер для постов категории
  • изменить дату активности автора
  • открыть категорию, если это был первый пост в ней
  • отправка письма автору [new]
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение noLogicOnlyWar »

ну не знаю, как мне кажется, тут операция одна, соответственно и сервис должен быть один.
Но сами вверху написали 2 операции :)
Если дублировать сервис, как по мне не очень хорошая идея т.к. там же может быть разная другая логика.
Например
пересчитать каунтер для постов категории
изменить дату активности автора
открыть категорию, если это был первый пост в ней
Вы это логику инкапсулируйте в сущности или доменном сервисе, или в обработчиках событий.
Как по мне это очень удобно. Например у меня есть две версии API и названия полей в них отличаются.
Как вариант да согласен, но обычно интерфейс выделяется после того как у вас появились 2 версии апи. Пока оно вам не надо, имхо это интерфейс на каждый чих писать, незачем.
Аватара пользователя
Йож
Сообщения: 574
Зарегистрирован: 2015.08.26, 03:05

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение Йож »

kolotek писал(а): 2017.11.30, 21:33Например добавить в класс метод fillDefault($post), или передавать $post в конструктор UpdatePostRequest и затем заполнять поля модели. Но такое решение не очень нравится.
Скажите, а там внутри будет как-то по-другому, нежели:
kolotek писал(а): 2017.11.30, 23:07

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

if($request->getTitle()!==null){
	$post->title=$request->getTitle();
}

if($request->getText()!==null){
	$post->text=$request->getText();
}

if($request->getCategory()!==null){
	$post->category=$request->getCategory()->id;
}

...etc
?
kolotek
Сообщения: 13
Зарегистрирован: 2017.06.27, 20:16

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение kolotek »

noLogicOnlyWar писал(а): 2017.12.01, 11:09 Но сами вверху написали 2 операции
Ну по идее же операция одна - обновление поста. Это же не важно, сколько у меня action-ов. Я могу запускать этот сервис из 5 экшенов, из консоли, из админки, с разных версий API. По идее же это не имеет значения, откуда вызывается операция, главное - операция, а это обновление поста. Нет?
noLogicOnlyWar писал(а): 2017.12.01, 11:09 Вы это логику инкапсулируйте в сущности или доменном сервисе, или в обработчиках событий.

Что вы имеете ввиду под доменном сервисом? Не смогли бы привезти примерный код описанного случая с использованием доменного сервиса?

Обработчик событий немного спорный момент т.к. уменьшается читаемость и дебаг кода. Если видишь в коде триггер какого-то ивента, нужно пойти и найти где и при каких событиях на этот ивент вешаются обработчики. Я думаю, что события нужно использовать только в тех моментах, где ты не контролируешь исполняемый код. Или хочешь дать возможность клиентскому коду немного модифицировать поведение компонента, или делать свои дополнительные операции в своих нуждах.
noLogicOnlyWar писал(а): 2017.12.01, 11:09 Как вариант да согласен, но обычно интерфейс выделяется после того как у вас появились 2 версии апи. Пока оно вам не надо, имхо это интерфейс на каждый чих писать, незачем.
Возможно. Но как правило, когда будут пилить вторую версию, то никто не захочет править первую версию, чтобы описать общие интерфейсы. Поэтому стараюсь это делать сразу, чтобы написание второй версии оказалось чуть менее болезненным.
kolotek
Сообщения: 13
Зарегистрирован: 2017.06.27, 20:16

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение kolotek »

Йож писал(а): 2017.12.01, 11:26
kolotek писал(а): 2017.11.30, 21:33Например добавить в класс метод fillDefault($post), или передавать $post в конструктор UpdatePostRequest и затем заполнять поля модели. Но такое решение не очень нравится.
Скажите, а там внутри будет как-то по-другому, нежели:
kolotek писал(а): 2017.11.30, 23:07

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

if($request->getTitle()!==null){
	$post->title=$request->getTitle();
}

if($request->getText()!==null){
	$post->text=$request->getText();
}

if($request->getCategory()!==null){
	$post->category=$request->getCategory()->id;
}

...etc
?
внутри сервиса будет нечто подобное:

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

public function execute($request){
	if(!$request->validate(){
		thow new ValidationException();
	}
	
	...
}
Аватара пользователя
Йож
Сообщения: 574
Зарегистрирован: 2015.08.26, 03:05

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение Йож »

Я вижу валидацию реквеста в Вашем посте, а я имел ввиду, что Вы все равно каждый параметр будете проверять на null в сервисе (чтоб он не перезаписывал нормальные данные). Или как-то по-другому?
kolotek
Сообщения: 13
Зарегистрирован: 2017.06.27, 20:16

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение kolotek »

Йож писал(а): 2017.12.02, 12:17 Я вижу валидацию реквеста в Вашем посте, а я имел ввиду, что Вы все равно каждый параметр будете проверять на null в сервисе (чтоб он не перезаписывал нормальные данные). Или как-то по-другому?
Ну собственно в этом и был мой вопрос - как поступить, чтобы постоянно не сравнивать значения с null. Например, один из вариантов, это заполнять реквест дефолтными данными. Например

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

$request=new UpdateRequest();
$request->fillDefault($post);
$service->execute($request);
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение ElisDN »

kolotek писал(а): 2017.12.03, 23:05 Например, один из вариантов, это заполнять реквест дефолтными данными.
Так и сделайте:

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

$request = UpdateRequest::fromPost($post);
...
$service->execute($request);
Аватара пользователя
Йож
Сообщения: 574
Зарегистрирован: 2015.08.26, 03:05

Re: Сервисы и объекты реквестов (модели), как быть?

Сообщение Йож »

Все равно приходим к тому, что нужно проверять на null, только уже тут: fromPost(). Иначе перезапишет, как и в первом посте.
Ответить