Слоистая архитектура для Yii приложений

Обсуждаем, как правильно строить приложения
Аватара пользователя
Roksalana
Сообщения: 213
Зарегистрирован: 2014.01.14, 09:34

Re: Слоистая архитектура для Yii приложений

Сообщение Roksalana » 2017.04.06, 08:18

vitovt писал(а):
2017.04.06, 08:07
Супер! И тут же в репозитории должны быть все действия над объектом? Сделать активным \ неактивным? Записать историю в лог-таблицу к примеру? Все, что касается изменений "заказа" все в репозиторий?
Репозиторий выполняет команды над AR моделью, ему говорят получи данные или сохрани - он выполняет. Но он не знает зачем он это делал. Знает только сервис. Сервис решает когда сделать модель не активной и дает команду репозиторию. Репозиторий выполняет указание сервиса. Сервис решает что нужно залогировать в лог-таблицу заказ, создать новую запись, поменять что-то в текущей и тп. Но он начальник, а всю черную работу будет делать репозиторий.

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.06, 09:15

Roksalana писал(а):
2017.04.06, 08:14
vitovt писал(а):
2017.04.05, 22:53
Вопрос: могу ли я в данном случае репозиторием возвращаться AR в котором будут определены методы getId(), getClientId() и так далее, верно? Пока что, на старте, потом пытаться отойти от этого.
Имхо, вызывать getId() в сервисном слое - плохая идея, т.к по всем сервисам расползутся вызовы AR модели. Если может репозиторий вернуть эти данные - пусть сразу возвращает. Если нет - то инкапсулировать в DTO объект (можно сразу несколько AR моделей передавать) и в нем собирать поля через getId(), getClientId(), сервис будет работать с этим объектом и ему все равно как именно поле расчитано. Завтра clientId будет перемещен (условно) в другую модель - поменяете один метод в нужно DTO и все работает дальше.
vitovt писал(а):
2017.04.05, 22:53
А уже в контрллере я могу сделать что-то вроде

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

$order = \Yii::$app->order->getById( 43 );

if( $order->isActive() ) {
  \Yii::$app->order->completeOrder($order);
}
В контроллере вызываем методы AR модели + та самая "бизнес логика" :o Это то от чего нужно уходить. Если от состояния модели зависят дальнейщие действия - это то о чем знает сервис: $order->isActive() => completeOrder - это пример "бизнес логики" в моем понимании. В контроллере получили данные (почистили, преобразовали и тп), передали сервисному слою, получили ответ (почистили, преобразовали и тп если надо) - вернули то что от нас ждут (html, json, xml и тп)
Имхо, вызывать getId() в сервисном слое - плохая идея

Тут я имел в виду, что репозиторий, чтобы получить, например, ID заказа, может дернуть $model->getId() а будет реализован этот метод в классе, наследуемом от AR или какой-то чистый DTO - уже вторично?

И второе, получается, что в контроллере я должен сделать что-то простое, типа

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

try {
   \Yii::$app->order->completeOrderById( 47 );
   }catch(\Exception $e) {
   	...
   }
а уже в сервисном слое сделать

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

public function completeOrderById(int $id) {
        $model = $this->findOne( $id );

        if( $model->isActive() ) {
            $this->completeOrder( $model );
        }
    }
    
    public function findOne( $id ) {
        return $this->orderRepository->findOne( $id );
    }
    
    public function completeOrder( $model ) {
        $this->orderRepository->updateAll(
            ...
        );
    }
или уже начинаю запутываться?

И еще вопрос транзакицй. Предпочитаю многие запросы оборачивать транзакциями, куда их в данном случае помещать (старт транзакции, роллбэк), получается в репозиторий? Но репозиторий делает простые команды, а транзакции отслеживают сразу 2-3 операции над базой. Тогда по логике транзакцию надо стартовать и коммитить в сервисном слое, но он же не должен знать откуда данные поступают к нему?
Последний раз редактировалось vitovt 2017.04.06, 09:25, всего редактировалось 2 раза.

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.06, 09:21

Roksalana писал(а):
2017.04.06, 08:18
vitovt писал(а):
2017.04.06, 08:07
Супер! И тут же в репозитории должны быть все действия над объектом? Сделать активным \ неактивным? Записать историю в лог-таблицу к примеру? Все, что касается изменений "заказа" все в репозиторий?
Репозиторий выполняет команды над AR моделью, ему говорят получи данные или сохрани - он выполняет. Но он не знает зачем он это делал. Знает только сервис. Сервис решает когда сделать модель не активной и дает команду репозиторию. Репозиторий выполняет указание сервиса. Сервис решает что нужно залогировать в лог-таблицу заказ, создать новую запись, поменять что-то в текущей и тп. Но он начальник, а всю черную работу будет делать репозиторий.
Получается, что репозиторий - это класс, который по сути умеет create, read, update, delete таким способом каким ему разрешено (файл, база, activerecord и т.д.)

Сервис делает все - вызывает объект, проверяет его, делает над ним какую-то работу (ставит активнм неактивным через команды репозиторию). Если раньше я делал так

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

Order extends ActiveRecord {
public function setActive() {
	self::updateAll(['active'=>1], "id='" . $this->id . "'");
}
}
то теперь это делает сервисный слой, через команду репозиторию? В таком случае у меня Order содержит только геттеры для доступа к данным (мы пока не говорим как они туда попадают, в случае с ActiveRecord - из базы при инициации, в случае DTO - через конструктор как я понимаю программист сам туда их загоянет).

Аватара пользователя
Roksalana
Сообщения: 213
Зарегистрирован: 2014.01.14, 09:34

Re: Слоистая архитектура для Yii приложений

Сообщение Roksalana » 2017.04.06, 10:03

Репозиторий может дернуть $model->getId(), а сервис не должен ничего дергать у AR модели.
Транзакции должны быть на уровне сервиса, так как это тоже часть "логики" (откатить в случаи неудачи на каком-то этапе). Но опять таки - сервис не начинает их сам и не заканчивает, а делает это через репозиторий (через вызовы методов). Тогда можно поменять схему хранения данных, не меняя логики (репозиторий будет реализовывать транзакции, к примеру сам в ручную запоминать операции для отката, если к примеру данные хранятся в файлах и там нет вообще системы транзакций).

anton_z
Сообщения: 378
Зарегистрирован: 2017.01.15, 15:01

Re: Слоистая архитектура для Yii приложений

Сообщение anton_z » 2017.04.06, 10:08

Тут репозиторий это не совсем репозиторий. Правильнее назвать его TableGateway. Через него будут выполняться все запросы к таблице, запись в которой представляет AR. Соответственно, сохранение связанных записей вместе с транзакциями ложится на сервис. В сервисах транзакцию стартуете, сохраняете записи (AR) в соответствующие таблицы, коммит. Тут конечно не DDD, но зависимости мокаются и с распределением обязанностей лучше.
Roksalana, правильные ли я сделал замечания? Или у вас репозиторий умеет связи сохранять?

Аватара пользователя
Roksalana
Сообщения: 213
Зарегистрирован: 2014.01.14, 09:34

Re: Слоистая архитектура для Yii приложений

Сообщение Roksalana » 2017.04.06, 10:10

vitovt писал(а):
2017.04.06, 09:21

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

Order extends ActiveRecord {
public function setActive() {
	self::updateAll(['active'=>1], "id='" . $this->id . "'");
}
}
то теперь это делает сервисный слой, через команду репозиторию? В таком случае у меня Order содержит только геттеры для доступа к данным (мы пока не говорим как они туда попадают, в случае с ActiveRecord - из базы при инициации, в случае DTO - через конструктор как я понимаю программист сам туда их загоянет).
Order может содержать такие методы, но экземпляр класса Order не должен "знать" о них = не должен вызывать такие методы сам у себя (кроме каких-то private методов, но это отдельная история). Такие методы, так же как и отношения моделей можно и нужно использовать, но вызывать их на уровне репозитория. Сам отдельный order - просто класс данных без методов (кроме геттеров).

Аватара пользователя
Roksalana
Сообщения: 213
Зарегистрирован: 2014.01.14, 09:34

Re: Слоистая архитектура для Yii приложений

Сообщение Roksalana » 2017.04.06, 10:16

anton_z писал(а):
2017.04.06, 10:08
Тут репозиторий это не совсем репозиторий. Правильнее назвать его TableGateway. Через него будут выполняться все запросы к таблице, запись в которой представляет AR. Соответственно, сохранение связанных записей вместе с транзакциями ложится на сервис. В сервисах транзакцию стартуете, сохраняете записи (AR) в соответствующие таблицы, коммит. Тут конечно не DDD, но зависимости мокаются и с распределением обязанностей лучше.
Roksalana, правильные ли я сделал замечания? Или у вас репозиторий умеет связи сохранять?
Да, тут репозиторий это не чистый репозиторий в терминах DDD :) Вообще это не структура DDD, зато более понятная и применимая для средне-крупных проектов. Сервис оперирует данными, конткретное хранение - дело репозитория, так что по поводу сохранения связей, имхо все зависит от задачи. Сервис не знает какие именно таблицы существует и не просит сохранить данные в таблицу A и таблицу B (так что тут не чистый TableGateway тоже), он просит "сохранить заказ", а куда - забота репозитория, но если нужно сохранить заказ, добавить задачу на упаковку и доставку, уведомить юзера об успешном заказе - то это уже будет делать сервис (раздавать указания в каком порядке кому что делать).

anton_z
Сообщения: 378
Зарегистрирован: 2017.01.15, 15:01

Re: Слоистая архитектура для Yii приложений

Сообщение anton_z » 2017.04.06, 10:23

Ну тут тогда проблема с сохранением связей тогда. Допустим у вас заказ с 100 позициями. Заказ был отредактирован - у него сняли четыре позиции, две добавили. При таком подходе, без UoW и проксирования при сохранении заказа придется вытянуть все позиции а затем пересохранить их.
С TableGateway тут лучше.
Последний раз редактировалось anton_z 2017.04.06, 10:49, всего редактировалось 2 раза.

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.06, 10:26

Roksalana писал(а):
2017.04.06, 10:03
Репозиторий может дернуть $model->getId(), а сервис не должен ничего дергать у AR модели.
Транзакции должны быть на уровне сервиса, так как это тоже часть "логики" (откатить в случаи неудачи на каком-то этапе). Но опять таки - сервис не начинает их сам и не заканчивает, а делает это через репозиторий (через вызовы методов). Тогда можно поменять схему хранения данных, не меняя логики (репозиторий будет реализовывать транзакции, к примеру сам в ручную запоминать операции для отката, если к примеру данные хранятся в файлах и там нет вообще системы транзакций).
Ага, получается, главная работа в сервисном слое, а репозиторий - некая прослойка, в данном случае для подключения к AR или базе напрямую.
Сервисный слой говорит: "- стартани мне транзакцию!", а в репозитории уже своя реализация этого метода (или чистый SQL или \Yii::$app->db->beginTransaction();)

Остальные методы вызываются также. Нужно что-то выбрать? Вызываем метод репозитория который возвращает некий объект или коллекцию объектов. В сервисе мы всегда знаем какого типа этот объект, какие у него есть методы.

Вот тут у меня и возник вопрос с getId()

Если я в сервисе делаю

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

$model = $this->orderRepository->findOne($id);
то теперь в сервисе я же могу сделать $model->getId() ? Я не знаю какой объект пришел в $model но я точно знаю что у него реализован getId() ? На этом примитивном примере хочу понять просто.

Тогда в случае смены хранилища, я меняю репозиторий, который, например, работает с MongoDB и он все равно возвращает мне модель у которой есть getId() просто теперь это не объект отнаследованный от AR а просто класс class Order { } в который я на уровне репозитория загнал нужные данные.

Например,

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

new Order($row['id'], DateTime($row['created_at']) .... );

anton_z
Сообщения: 378
Зарегистрирован: 2017.01.15, 15:01

Re: Слоистая архитектура для Yii приложений

Сообщение anton_z » 2017.04.06, 10:29

Про смену хранилища лучше не думайте - это бывает крайне редко и все равно требует огромного рефакторинга из-за рассогласования нагрузки.
Транзакции надо стартовать и коммитить с помощью отдельного сервиса (TransactionManager. какой-нибудь), потому что могут быть транзации на несколько репозиториев.

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.06, 10:45

anton_z писал(а):
2017.04.06, 10:29
Про смену хранилища лучше не думайте - это бывает крайне редко и все равно требует огромного рефакторинга из-за рассогласования нагрузки.
Транзакции надо стартовать и коммитить с помощью отдельного сервиса (TransactionManager. какой-нибудь), потому что могут быть транзации на несколько репозиториев.
Да, точно!

Аватара пользователя
slavcodev
Сообщения: 3133
Зарегистрирован: 2009.04.02, 21:42
Откуда: Altea, Spain
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение slavcodev » 2017.04.06, 14:00

Добавлю свои пять копеек

1) DTO - объект данных передающийся между слоями. Создавать его в контролере, в Presentation Layer (т.е. самом крайнем) безполезно. Скорее всего это ДТО должен возвращаться из сервисов.

2) Декораторы над DTO? Для чего это может быть полезно?

3) Generic DTO + костыльные декораторы? Для чего эти сложности? Для "архитектуры"? Специфичные DTO сделают код лучше.

4) DTO::make, Decorator::decorate() - чем отличаются/лучше использования конструктора? Мода на статические метода - фабрики?

ЗЫ: +1 к тому что слишком много магии в коде, и UserRepository - это не понятно что.
Жду Yii 3!

anton_z
Сообщения: 378
Зарегистрирован: 2017.01.15, 15:01

Re: Слоистая архитектура для Yii приложений

Сообщение anton_z » 2017.04.06, 15:40

slavcodev писал(а):
2017.04.06, 14:00
1) DTO - объект данных передающийся между слоями. Создавать его в контролере, в Presentation Layer (т.е. самом крайнем) безполезно. Скорее всего это ДТО должен возвращаться из сервисов.
А что предлагаете вместов него? AR? Форму с горой зависимостей в сервис отдавать?
Профит такой, что dto хорошо сериализуются, легко перейти к шине команд в случае чего или залоггировать.
Еще dto зависит от контекста. Если операция меняет 10 свойств у сущности, которая имеет 100 свойств, лучше давать доступ только к тому, что нужно для выполнения операции
slavcodev писал(а):
2017.04.06, 14:00
2) Декораторы над DTO? Для чего это может быть полезно?
Для форматирования, чтобы в шаблонах всякие форматтеры не вызывать, не писать горы кода по преобразованию в нужный формат, а все сделать в специально предназначенном для этого классе
slavcodev писал(а):
2017.04.06, 14:00
3) Generic DTO + костыльные декораторы? Для чего эти сложности? Для "архитектуры"? Специфичные DTO сделают код лучше.
Ответ на п.2 здесь подойдет.
slavcodev писал(а):
2017.04.06, 14:00
4) DTO::make, Decorator::decorate() - чем отличаются/лучше использования конструктора? Мода на статические метода - фабрики?
Это паттерн named constructor. Позволяет сделать несколько конструкторов. В PHP нет перегрузки функций.
slavcodev писал(а):
2017.04.06, 14:00
ЗЫ: +1 к тому что слишком много магии в коде, и UserRepository - это не понятно что.
Где тут магия? Ни __get() ни __invoke(), ничего такого нет. Просто более грамотно распределили обязанности. Классов стало больше. Но они простые. С AR сняли обязанность по сохранению самой себя в БД, передав ее сервису.

Да, тут есть вопросы по обязанностям данных "репозиториев" - должны ли они сохранять одну запись или несколько. Если несколько - могут проблемы с реализацией возникнуть. Я склонен считать данные "репозитории" более близкими к TableGateway, почти забытому ныне паттерну.
Данный подход куда лучше, чем делать "как придется", как делает большинство.

Мне вот очень зендовский подход нравится - просто и тестировать можно, ну и плевать, что есть exchangeArray():

https://docs.zendframework.com/tutorial ... nd-models/

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.07, 00:00

Вот кстати любопытная ссылка попалась чтобы понимать как проектировать тот или иной слой

http://stackoverflow.com/questions/1617 ... ign-in-php

Но в этом примере контроллер работает сразу через репозиторий, что, как я понимаю, не всегда удобно? Или так можно было? =)

Тогда в сервисном слое получается бОльшая часть методово совпадает с методами в репозитории.
Последний раз редактировалось vitovt 2017.04.07, 07:35, всего редактировалось 1 раз.

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.07, 07:29

Я думаю, сложно начать применять слоистую архитектуру, по крайней мере для меня, становится тот факт, что работая с Yii очень легко поддаться на удобство, когда ты в контроллере делаешь что-то вроде

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

$dataProvider = (new OrderSearch())->search($params); 
и реализуешь в методе search() стандартный dataProvider

и это легко и просто, а вот когда хочешь перейти на сервисы, репозитории, сущности, возникает вопрос как это все дело менять, пейджинги, филтрации, вот это все.

(полагаю, что также но внутри есть ощущение что что-то не так)

Но для стандартных вещей, конечно легко -

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

\Yii::$app->order->findOne(123);
или там

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

\Yii::$app->order->getAllItemsByOrderId(123); 
Вот эти вещи как раз из статьи и непонятны. И понятно дело почему все ищут какой-то хороший пример: чтобы как раз подсмотреть реализацию нестандартных вещей.

Аватара пользователя
maleks
Сообщения: 1714
Зарегистрирован: 2012.12.26, 12:56

Re: Слоистая архитектура для Yii приложений

Сообщение maleks » 2017.04.07, 08:14

vitovt писал(а):
2017.04.07, 07:29
Но для стандартных вещей, конечно легко -

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

\Yii::$app->order->findOne(123);
Если order - это у вас сервис, непонятно почему вы его используете как yii компонент. В статье ни о чем таком не говорилось.
anton_z писал(а):
2017.04.06, 15:40
slavcodev писал(а):
2017.04.06, 14:00
ЗЫ: +1 к тому что слишком много магии в коде, и UserRepository - это не понятно что.
Где тут магия? Ни __get() ни __invoke(), ничего такого нет. Просто более грамотно распределили обязанности. Классов стало больше. Но они простые. С AR сняли обязанность по сохранению самой себя в БД, передав ее сервису.
Не вижу где в статье инфа о сохранении AR из сервиса, и что конкретно это значит? Как то не через $ArModel->save() ?

anton_z
Сообщения: 378
Зарегистрирован: 2017.01.15, 15:01

Re: Слоистая архитектура для Yii приложений

Сообщение anton_z » 2017.04.07, 09:18

В статье нет прям такого примера. Репозиторий сохраняет объекты по определению. Иначе он не репозиторий вообще.

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.07, 09:21

maleks писал(а):
2017.04.07, 08:14
vitovt писал(а):
2017.04.07, 07:29
Но для стандартных вещей, конечно легко -

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

\Yii::$app->order->findOne(123);
Если order - это у вас сервис, непонятно почему вы его используете как yii компонент. В статье ни о чем таком не говорилось.

Так можно сказать сложилось исторически, приходится с этим работать да и потом, работая с Yii пока что так удобнее. Вот уже сам компонент (он как бы прослойка между Yii и всеми остальными слоями).

Если не правильно то как надо тогда?

Как я писал выше кто-то сразу в контроллере лезет в репозиторий

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

class SiteController extends Controller {
	public function __construct(OrderRepository $repository) {
		$this->repository = $repository;
	}
}
но тогда в случае смены репозитория надо везде во всех контроллерах менять код, а так только в одном сервисном слое. Нет? Да и в конторллере может понадобится не 1 репозитоий а 2 -5 (заказы, товары в заказе, клиенты и т.д.).

Аватара пользователя
vitovt
Сообщения: 210
Зарегистрирован: 2012.03.21, 10:37
Контактная информация:

Re: Слоистая архитектура для Yii приложений

Сообщение vitovt » 2017.04.07, 09:23

anton_z писал(а):
2017.04.07, 09:18
В статье нет прям такого примера. Репозиторий сохраняет объекты по определению. Иначе он не репозиторий вообще.
На каком-то форуме услышал мнение, что репозиторий - это только отображение коллекции. Тогда вообще стало не понятно, а где же сохранять сущность свою. Но здесь мнение о том что репозиторий должен сохранять как-то даже логично звучит.

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Слоистая архитектура для Yii приложений

Сообщение zelenin » 2017.04.07, 09:23

vitovt писал(а):
2017.04.07, 09:21
Как я писал выше кто-то сразу в контроллере лезет в репозиторий

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

class SiteController extends Controller {
	public function __construct(OrderRepository $repository) {
		$this->repository = $repository;
	}
}
но тогда в случае смены репозитория надо везде во всех контроллерах менять код, а так только в одном сервисном слое. Нет? Да и в конторллере может понадобится не 1 репозитоий а 2 -5 (заказы, товары в заказе, клиенты и т.д.).
так интерфейс должен быть прописан, а не реализация - тогда менять ничего не придется.

Ответить