Слоистая архитектура на основе сервисов

Обсуждаем, как правильно строить приложения
Ответить
nexus
Сообщения: 10
Зарегистрирован: 2017.03.23, 14:43

Слоистая архитектура на основе сервисов

Сообщение nexus »

Всем привет. Есть проект на yii1, где куча лапша кода. Решил перевести его на yii2 и сделать это максимально правильно с точки зрения архитектуры. Почитав это ветку на форуме, решил делать слоистую архитектуру на основе сервисов, но опыта особого нет в этом. Принял решение делать на каждое действие отдельные сервисы, где инкапсулировать всю бизнес логику.

В связи с этим появилось множество вопросов.

В yii1 есть AR модель Project, в которой хранится множество атрибутов из таблицы. Поэтому первым делом решил сгруппировать их по смыслу и вынести в отдельные классы VO, тем самым избавиться от множества параметров.

Далее создал сервис ProjectUpdateService который занимается обновлением агрегата Project. В приложении есть frontend и api. Если во frontend запрос на редактирование приходит одной большой формой со всеми атрибутами агрегата, то в api можно обновить как все атрибуты, так и любой один параметр.

На сколько я понял, на вход сервис должен принимать DTO, так как нужно передавать большое количество параметров. В контроллере использую отдельную yii-ную форму ProjectUpdateForm, где происходит валидация данных. Можно ли это форму использовать как DTO и передавать ее в сервис? Как в таком случае редактировать только один параметр агрегата через REST API. Использовать эту же форму, только предварительно загрузить в нее данные из агрегата и лишь потом из реквеста?

Как реализовать обновление только того, что нужно, а не всего агрегата?

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

Re: Слоистая архитектура на основе сервисов

Сообщение ElisDN »

nexus писал(а): 2017.03.23, 17:23 Можно ли это форму использовать как DTO и передавать ее в сервис?
Можно. Это избавит от дублирования.
nexus писал(а): 2017.03.23, 17:23 Как в таком случае редактировать только один параметр агрегата через REST API. Использовать эту же форму, только предварительно загрузить в нее данные из агрегата и лишь потом из реквеста? Как реализовать обновление только того, что нужно, а не всего агрегата?
Да, той же целиковой формой.
nexus
Сообщения: 10
Зарегистрирован: 2017.03.23, 14:43

Re: Слоистая архитектура на основе сервисов

Сообщение nexus »

А в сервис должны приходить уже валидные данные или валидацию нужно делать в самом сервисе? Или можно базовую валидацию делать в контроллере формой, а низкоуровневую валидацию уже в самом сервисе?
Так как 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' => 'Еще что-то',
]);
Можно ли использовать такой подход?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Слоистая архитектура на основе сервисов

Сообщение ElisDN »

nexus писал(а): 2017.03.23, 23:26 А в сервис должны приходить уже валидные данные или валидацию нужно делать в самом сервисе?
viewtopic.php?f=34&t=36725&start=20#p188218
nexus писал(а): 2017.03.23, 23:26 Так как dto используется для переноса данных между слоями и содержит гетеры и сетеры, можно ли использовать такой код
Сделайте public-поля или дёргайте геттеры явно. С магическим __get не работает автоподстановка.
nexus
Сообщения: 10
Зарегистрирован: 2017.03.23, 14:43

Re: Слоистая архитектура на основе сервисов

Сообщение nexus »

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

Re: Слоистая архитектура на основе сервисов

Сообщение ElisDN »

nexus писал(а): 2017.03.24, 10:45 Спасибо за ответ. Еще такой вопрос, для сохранения моего агрегата нужно делать какой-то трансформер, который будет приводить агрегат в плоскую форму, а также для создания агрегата при извлечении из базы?
viewtopic.php?f=34&t=42318
nexus писал(а): 2017.03.24, 10:45 Получается при редактировании для установки первоначальных данных для формы нужно проделывать такую же операцию. Можно ли использовать один и тот же трансформер?
А у Вас поля в БД с полями в форме совпадают?
nexus
Сообщения: 10
Зарегистрирован: 2017.03.23, 14:43

Re: Слоистая архитектура на основе сервисов

Сообщение nexus »

ElisDN писал(а): 2017.03.24, 12:48 А у Вас поля в БД с полями в форме совпадают?
Если рассматривать вариант, когда поля совпадают, то можно использовать?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Слоистая архитектура на основе сервисов

Сообщение ElisDN »

nexus писал(а): 2017.03.24, 13:08 Если рассматривать вариант, когда поля совпадают, то можно использовать?
Попробуйте.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Слоистая архитектура на основе сервисов

Сообщение anton_z »

Также рефакторю проект на yii1. Я бы не стал использовать класс формы в качестве dro. Слишком много обязанностей. Обратите внимание на симфонию и зенд - там формы о тдельно от всего. Делаю так: форма > dto > сервис приложения > доменная сущность/сущности > репозиторий > бд.
nexus
Сообщения: 10
Зарегистрирован: 2017.03.23, 14:43

Re: Слоистая архитектура на основе сервисов

Сообщение nexus »

Хотелось бы минимизировать дублирование свойст в форме, dto и сущности. Получается, чтобы добавить одно свойство нужно будет добавлять его в кучу мест. Как вы с этим боретесь? Используете магические методы?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Слоистая архитектура на основе сервисов

Сообщение zelenin »

nexus писал(а): 2017.03.25, 00:10 Хотелось бы минимизировать дублирование свойст в форме, dto и сущности. Получается, чтобы добавить одно свойство нужно будет добавлять его в кучу мест. Как вы с этим боретесь? Используете магические методы?
использование магии по умолчанию минимизируют в хороших домах.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Слоистая архитектура на основе сервисов

Сообщение anton_z »

nexus писал(а): 2017.03.25, 00:10 Хотелось бы минимизировать дублирование свойст в форме, dto и сущности. Получается, чтобы добавить одно свойство нужно будет добавлять его в кучу мест. Как вы с этим боретесь? Используете магические методы?
Это не то с чем надо бороться
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Слоистая архитектура на основе сервисов

Сообщение SiZE »

nexus писал(а): 2017.03.23, 23:26 А в сервис должны приходить уже валидные данные или валидацию нужно делать в самом сервисе? Или можно базовую валидацию делать в контроллере формой, а низкоуровневую валидацию уже в самом сервисе?
Я обычно использую упрощенный вариант того, что писал 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');
       }
   }
}
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Слоистая архитектура на основе сервисов

Сообщение ElisDN »

SiZE писал(а): 2017.03.25, 08:35 Я обычно использую упрощенный вариант того, что писал ElisDN
Это откуда Вы такой пример со статическими методами и валидацией в сервисе взяли? Я про такое обычно говорил:

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

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]);
}
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Слоистая архитектура на основе сервисов

Сообщение SiZE »

ElisDN писал(а): 2017.03.25, 08:58 Это откуда Вы такой пример со статическими методами и валидацией в сервисе взяли? Я про такое обычно говорил:
Я ж говорю "упрощенный" :)
nexus
Сообщения: 10
Зарегистрирован: 2017.03.23, 14:43

Re: Слоистая архитектура на основе сервисов

Сообщение nexus »

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');
       }
   }
}

У этого подхода есть один минус, нельзя вывести несколько ошибок одновременно, а только одну из исключения.
Melodic
Сообщения: 87
Зарегистрирован: 2016.05.11, 17:43
Откуда: Луганск

Re: Слоистая архитектура на основе сервисов

Сообщение Melodic »

nexus писал(а): 2017.03.25, 11:54
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');
       }
   }
}

У этого подхода есть один минус, нельзя вывести несколько ошибок одновременно, а только одну из исключения.
Ничего не мешает создать свой класс исключения, который сможет хранить все ошибки валидации.
Аватара пользователя
SiZE
Сообщения: 2813
Зарегистрирован: 2011.09.21, 12:39
Откуда: Perm
Контактная информация:

Re: Слоистая архитектура на основе сервисов

Сообщение SiZE »

nexus писал(а): 2017.03.25, 11:54 У этого подхода есть один минус, нельзя вывести несколько ошибок одновременно, а только одну из исключения.
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 содержит ошибки, обрабатываем как хотим
}
Можно передавать исключению модель, можно кидать массив ошибок и тд и тп.
Ответить