Слоистая архитектура на основе сервисов
Слоистая архитектура на основе сервисов
Всем привет. Есть проект на yii1, где куча лапша кода. Решил перевести его на yii2 и сделать это максимально правильно с точки зрения архитектуры. Почитав это ветку на форуме, решил делать слоистую архитектуру на основе сервисов, но опыта особого нет в этом. Принял решение делать на каждое действие отдельные сервисы, где инкапсулировать всю бизнес логику.
В связи с этим появилось множество вопросов.
В yii1 есть AR модель Project, в которой хранится множество атрибутов из таблицы. Поэтому первым делом решил сгруппировать их по смыслу и вынести в отдельные классы VO, тем самым избавиться от множества параметров.
Далее создал сервис ProjectUpdateService который занимается обновлением агрегата Project. В приложении есть frontend и api. Если во frontend запрос на редактирование приходит одной большой формой со всеми атрибутами агрегата, то в api можно обновить как все атрибуты, так и любой один параметр.
На сколько я понял, на вход сервис должен принимать DTO, так как нужно передавать большое количество параметров. В контроллере использую отдельную yii-ную форму ProjectUpdateForm, где происходит валидация данных. Можно ли это форму использовать как DTO и передавать ее в сервис? Как в таком случае редактировать только один параметр агрегата через REST API. Использовать эту же форму, только предварительно загрузить в нее данные из агрегата и лишь потом из реквеста?
Как реализовать обновление только того, что нужно, а не всего агрегата?
Заранее спасибо за помощь.
В связи с этим появилось множество вопросов.
В yii1 есть AR модель Project, в которой хранится множество атрибутов из таблицы. Поэтому первым делом решил сгруппировать их по смыслу и вынести в отдельные классы VO, тем самым избавиться от множества параметров.
Далее создал сервис ProjectUpdateService который занимается обновлением агрегата Project. В приложении есть frontend и api. Если во frontend запрос на редактирование приходит одной большой формой со всеми атрибутами агрегата, то в api можно обновить как все атрибуты, так и любой один параметр.
На сколько я понял, на вход сервис должен принимать DTO, так как нужно передавать большое количество параметров. В контроллере использую отдельную yii-ную форму ProjectUpdateForm, где происходит валидация данных. Можно ли это форму использовать как DTO и передавать ее в сервис? Как в таком случае редактировать только один параметр агрегата через REST API. Использовать эту же форму, только предварительно загрузить в нее данные из агрегата и лишь потом из реквеста?
Как реализовать обновление только того, что нужно, а не всего агрегата?
Заранее спасибо за помощь.
Re: Слоистая архитектура на основе сервисов
Можно. Это избавит от дублирования.
Да, той же целиковой формой.
Re: Слоистая архитектура на основе сервисов
А в сервис должны приходить уже валидные данные или валидацию нужно делать в самом сервисе? Или можно базовую валидацию делать в контроллере формой, а низкоуровневую валидацию уже в самом сервисе?
Так как dto используется для переноса данных между слоями и содержит гетеры и сетеры, можно ли использовать такой код
Если свойств много, чтобы не делать для каждого свой гетер и не передавать кучу параметров в конструктор, то можно использовать так:
Можно ли использовать такой подход?
Так как dto используется для переноса данных между слоями и содержит гетеры и сетеры, можно ли использовать такой код
Код: Выделить всё
abstract class Dto
{
public function __construct(array $params)
{
$attributes = get_class_vars(get_class($this));
foreach ($params as $attribute => $value) {
if (array_key_exists($attribute, $attributes)) {
$this->$attribute = $value;
}
}
}
public function __get($name)
{
if (property_exists($this, $name)) {
return $this->$name;
}
$getter = 'get' . $name;
if (method_exists($this, $getter)) {
return $this->$getter();
}
throw new \DomainException('Getting unknown property: ' . get_class($this) . '::' . $name);
}
}
class ProjectDto extends Dto
{
protected $name;
protected $description;
*
*
*
protected $description2;
}
Код: Выделить всё
$dto = new ProjectDto ([
'name' => 'Название',
'description' => 'Описание',
*
*
*
'description2' => 'Еще что-то',
]);
Re: Слоистая архитектура на основе сервисов
viewtopic.php?f=34&t=36725&start=20#p188218
Сделайте public-поля или дёргайте геттеры явно. С магическим __get не работает автоподстановка.
Re: Слоистая архитектура на основе сервисов
Спасибо за ответ. Еще такой вопрос, для сохранения моего агрегата нужно делать какой-то трансформер, который будет приводить агрегат в плоскую форму, а также для создания агрегата при извлечении из базы?
Получается при редактировании для установки первоначальных данных для формы нужно проделывать такую же операцию. Можно ли использовать один и тот же трансформер?
Получается при редактировании для установки первоначальных данных для формы нужно проделывать такую же операцию. Можно ли использовать один и тот же трансформер?
Re: Слоистая архитектура на основе сервисов
Re: Слоистая архитектура на основе сервисов
Также рефакторю проект на yii1. Я бы не стал использовать класс формы в качестве dro. Слишком много обязанностей. Обратите внимание на симфонию и зенд - там формы о тдельно от всего. Делаю так: форма > dto > сервис приложения > доменная сущность/сущности > репозиторий > бд.
Re: Слоистая архитектура на основе сервисов
Хотелось бы минимизировать дублирование свойст в форме, dto и сущности. Получается, чтобы добавить одно свойство нужно будет добавлять его в кучу мест. Как вы с этим боретесь? Используете магические методы?
Re: Слоистая архитектура на основе сервисов
использование магии по умолчанию минимизируют в хороших домах.
Re: Слоистая архитектура на основе сервисов
Я обычно использую упрощенный вариант того, что писал ElisDN
Код: Выделить всё
public function actionCreate()
{
$form = new CreateForm();
if ($form->load(Yii::$app->request->post())) {
try {
SomeService::createFromCreateForm($form);
} catch (\LogicException $e) {
// обработка по желанию
}
}
}
Код: Выделить всё
class SomeService
{
public static function createFromCreateForm(CreateForm $form)
{
if ($form->validate()) {
throw new \DomainException('Model is not valid');
}
}
}
Re: Слоистая архитектура на основе сервисов
Это откуда Вы такой пример со статическими методами и валидацией в сервисе взяли? Я про такое обычно говорил:
Код: Выделить всё
public function actionCreate()
{
$form = Yii::createObject(ProductCreateForm::className());
if ($form->load(Yii::$app->request->post()) && $form->validate()) {
try {
$this->productService->create(Yii::$app->user->id, $form);
return $this->redirect(['index']);
} catch (\DomainException $e) {
...
Yii::$app->session->setFlash('error', $e->getMessage());
}
}
return $this->render('index', ['form' => $form]);
}
Re: Слоистая архитектура на основе сервисов
SiZE писал(а): ↑2017.03.25, 08:35 Я обычно использую упрощенный вариант того, что писал ElisDN
Код: Выделить всё
public function actionCreate() { $form = new CreateForm(); if ($form->load(Yii::$app->request->post())) { try { SomeService::createFromCreateForm($form); } catch (\LogicException $e) { // обработка по желанию } } }
Код: Выделить всё
class SomeService { public static function createFromCreateForm(CreateForm $form) { if ($form->validate()) { throw new \DomainException('Model is not valid'); } } }
У этого подхода есть один минус, нельзя вывести несколько ошибок одновременно, а только одну из исключения.
Re: Слоистая архитектура на основе сервисов
Ничего не мешает создать свой класс исключения, который сможет хранить все ошибки валидации.nexus писал(а): ↑2017.03.25, 11:54SiZE писал(а): ↑2017.03.25, 08:35 Я обычно использую упрощенный вариант того, что писал ElisDN
Код: Выделить всё
public function actionCreate() { $form = new CreateForm(); if ($form->load(Yii::$app->request->post())) { try { SomeService::createFromCreateForm($form); } catch (\LogicException $e) { // обработка по желанию } } }
Код: Выделить всё
class SomeService { public static function createFromCreateForm(CreateForm $form) { if ($form->validate()) { throw new \DomainException('Model is not valid'); } } }
У этого подхода есть один минус, нельзя вывести несколько ошибок одновременно, а только одну из исключения.
Re: Слоистая архитектура на основе сервисов
Why not? Вариантов много, самый простой:
Код: Выделить всё
class InvalidModelException extends DomainException
{
}
Код: Выделить всё
public static function createFromCreateForm(CreateForm $form)
{
if (!$form->validate()) {
throw new InvalidModelException();
}
}
Код: Выделить всё
try {
SomeService::createFromCreateForm($form);
} catch (InvalidModelException $e) {
// $form содержит ошибки, обрабатываем как хотим
}