Как стать автором
Обновить

Комментарии 14

Пока что не совсем понятны преимущества такого подхода даже перед самым простым решением в лоб — запилить какой-то класс сервис, который будет заниматься созданием Книги, и дерганьем других сервисов, типа Email-sender`а если его нужно отправить Email при создании книги.
У такого сервиса будет хотя бы явно-определенный интерфейс с типизацией.

Зона ответственности класса так же весьма размыта. Судя по коду из базового трейта Интерактора автор уже как минимум предлагает прикручивать к реализации валидацию входящего запроса.
Код
<?php
namespace Lib\Interactor;
trait Interactor
{
    abstract function call();
    
    public function __invoke()
    {
        $arguments = func_get_args();
        $payload = [];
        if (method_exists($this, 'validate') && !$this->validate(...$arguments)) 
        {
            return new InteractorResult($payload, false); 
        }
        static::call(...$arguments);
        foreach ((static::$expose ?? []) as $expose) {
            $payload[$expose] = $this->{$expose};
        }
        
        return new InteractorResult($payload, true);
    }
}


Думаю, чтобы пустить такое решение в продакшен должны быть действительно веские аргументы. Я не представляю как понять что он делает не запустив его, или не покопавшись в документации по магическим методам.
Пока что не совсем понятны преимущества такого подхода даже перед самым простым решением в лоб — запилить какой-то класс сервис, который будет заниматься созданием Книги, и дерганьем других сервисов, типа Email-sender`а если его нужно отправить Email при создании книги.
У такого сервиса будет хотя бы явно-определенный интерфейс с типизацией.

Проблема «сервисов» в том, что само их определение достаточно размытое и каждый может вкладывать в этот термин свой смысл. Интерактор (или же «операция») — по факту и есть максимально узко специализированный «класс-сервис», отвечающий за конкретную операцию бизнес-процесса. Интерфейс интерактора в данном случае — один метод call. Типизация (type hinting) к сожалению потерялась в угоду другим плюшкам. Это особенность конкретной реализации интерактора. Сам же трэйт тут нужен по двум причинам — что бы примеры кода в тексте были максимально приближены к исходному тексту, и для уменьшения количества шаблонного кода плюс единообразный интерфейс всех интеракторов.

Зона ответственности класса так же весьма размыта. Судя по коду из базового трейта Интерактора автор уже как минимум предлагает прикручивать к реализации валидацию входящего запроса.

Нет, валидация именно входящего запроса не предполагается. Валидация входных параметров может быть частью операции и реализует «return early» паттерн. Например, на вход может подаваться результат другого интерактора.

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

Это всего лишь реакция «триггер» на наличие магических методов и чего то нового. Если мы имеем некий AddBookService, это точно так же ничего не говорит нам о том, что он делает и как его запустить не покопавшись в документации (или исходном коде). Когда в тоже самое время реализация интеракторов рассмотренная в тексте, сводит интерфейс класса к одному единственному публичному методу (call\__invoke). Некая «магия» конечно присутствует, но без этого не обходится ни один современный фрэймворк, так как эта магия призвана упрощать нам жизнь.

Ну и тащить что то в продакшен тут не предлагается, цель текста — рассмотреть и обсудить паттерн, показать простейший пример разработки через тестирование.
Нет, валидация именно входящего запроса не предполагается. Валидация входных параметров может быть частью операции и реализует «return early» паттерн.

Я не силён в этих ваших паттернах, но не нарушает ли такое поведение указанный же выше принцип единственной ответственности?
«return early» относится больше к coding style и говорит лишь о том, что оператор return (с негативным результатом) должен находиться как можно раньше в теле функции.
Краткий пример (псевдокод):
if (valid($params)) {
  doSomething()
} else {
  ...
}

//return early

if (!valid($params)) {
  return
}
doSomething()

Вопрос относится к валидации, а не к return
Прошу прощения. Тогда ответ будет такой — смотря как мы определим эту самую злополучную «единственную ответственность», и что будет находиться внутри валидации. В любом случае, валидация — не обязательна и мы можем делегировать её другому классу (в том числе в другому интерактору).
Здесь же она скорей нужна для использования интеракторов в цепочке:
result = doSomething(params) //вернул negative result
result = doSomething2(result) // проверили, что на вход пришёл negative result и вернули его же
Здесь же она скорей нужна для использования интеракторов в цепочке:


Почему бы просто не использовать старые добрые исключения? Будет один блок try/catch в котором точно так же цепочкой записаны все вызовы, плюс не будет лишнего передёргивания функций, если где-то пришёл отрицательный результат.
Проблема «сервисов» в том, что само их определение достаточно размытое и каждый может вкладывать в этот термин свой смысл. Интерактор (или же «операция») — по факту и есть максимально узко специализированный «класс-сервис», отвечающий за конкретную операцию бизнес-процесса.

Я не вижу никаких проблем в обычных классах-сервисах которые аггрегируют внутри себя вызовы других сервисов, определяя какой-то сценарий. С Интеракторами у нас получается тоже самое, но с исковерканным не понятно для чего интерфейсом.
Ну то есть да, если бы я такое увидел у меня точно рука не поднялась добавлять в эту странную штуку какие-то свои методы, помимо call(), но такие вещи должны отсекаться на уровне code review, а от добавления в Интеракторы какой-нибудь не подходщей для них логики неопытным программистом всё-равно никто нас не застрахует.
Для пущего спокойствия можно инжектить обычные сервисы через единый интерфейс с каким-нибудь одним публичным методом типа call();
НЛО прилетело и опубликовало эту надпись здесь
У Command немного другая сигнатура и назначение. «Команда» en.wikipedia.org/wiki/Command_pattern позволяет проводить отложенные операции (аргументы вызова передаются в конструктор) и производить их отмену.

Поздравляю, вы придумали command bus, только без bus.
https://laravel.com/docs/5.0/bus
Хотя, там нет возвращаемого значения, а значит это скорее процедура, чем функция.


Ну или вот Actions из yii
https://www.yiiframework.com/doc/api/2.0/yii-base-action
Прям почти тоже самое.


Вот только что это меняет?


А собственно ничего. Фактически, в вашей реализации, это сервис с одним методом.


Какая мне разница, тестировать один сервис с десятью методами, или десять сервисов с одним методом?


Удобнее ли это? Кому-то да, особенно функциональщикам — они любят накомпорзировать всякого, а потом гонять скомпоженное туда-сюда. А кому-то и нет. Потому-как (здравствуйте), в Java (откуда, судя по всему, это поделие и привалило к нам), в отличии от php, имеются дженерики, и можно обозначить возвращаемое значение например как InteractorResult<User>, тем самым явно определив содержимое результата. В php же для сохранения явной типизации, придется плодить наследников типа class InteractorUserResult extends InteracotrResult… дичь, в общем.


Идея нормальная, но надо оправить ее обратно в Java. Пусть приходит, когда в php дженериков завезут.

Вот только что это меняет?
В целом это всё схожие паттерны.
Всё остальное в статье. Пришло это не из java, а из ruby (а туда надо полагать из функциональщины). Один сервис с десятью методами запросто превращается в God Object.
Вот с типизацией действительно есть некоторая проблема, дженерики тут немного мимо, InteractorResult — уже сам по себе контейнер, вместо него мог быть какой нибудь именованный tuple.

God Object получается от нарушения принципов Single Responsibility, а не от того, что заместо одного метода в сервисе оказалось два.


… дженерики тут немного мимо… какой нибудь именованный tuple.

Не понимаю, почему дженерики мимо и при чем тут кортежи.

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории