command bus !== ООП?

Обсуждаем, как правильно строить приложения
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

command bus !== ООП?

Сообщение Bio man »

Видел на форуме пример ValidationCommandBus, который бросает исключение валидации. Так вот, это прямое нарушение LSP. Получается, что контракт базового класса (интерфейса) не соблюдается, т.к. интерфейс должен четко регламентировать, какие исключения могут быть обработаны. Отсюда следует, что класс использующий CommandBusInterface должен знать о деталях конкретной реализации (ValidationCommandBus), от чего целесообразность использования интерфейса теряется.
http://sergeyteplyakov.blogspot.com/201 ... ciple.html

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

Как можно избежать данных критических ограничений или, хотя бы, минимизировать последствия?

Обсуждаем) :idea:
Nex-Otaku
Сообщения: 831
Зарегистрирован: 2016.07.09, 21:07

Re: command bus !== ООП?

Сообщение Nex-Otaku »

интерфейс должен четко регламентировать, какие исключения могут быть обработаны
На чём основывается это утверждение?
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

Nex-Otaku писал(а): 2018.01.16, 10:18
интерфейс должен четко регламентировать, какие исключения могут быть обработаны
На чём основывается это утверждение?
На соблюдении контракта. По ссылочке почитай.

Я не говорю о runtime exception, который может вылететь от куда угодно. Но исключения, которые выбрасывает шина, должны быть задукоментированы, т.е. должны быть частью контракта.
Если мы работаем с интерфейсом и используем реализацию, которая бросает исключение, которое не указано в контракте, то мы вынуждены знать о деталях наследника, что бы обработать исключение (например, исключение валидации).
Последний раз редактировалось Bio man 2018.01.16, 10:24, всего редактировалось 1 раз.
Аватара пользователя
ElisDN
Сообщения: 5841
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: command bus !== ООП?

Сообщение ElisDN »

...
Последний раз редактировалось ElisDN 2018.01.16, 10:30, всего редактировалось 2 раза.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: command bus !== ООП?

Сообщение zelenin »

command bus !== ООП.
Причем здесь ООП, если дальше вы пишете про LSP?

Исключения должны кидаться при исключительных, непредусматриваемых кодом, ситуациях. Валидация - это штатная функция. Она должна возвращать результат валидации.
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

zelenin писал(а): 2018.01.16, 10:25
command bus !== ООП.
Причем здесь ООП, если дальше вы пишете про LSP?
Хорошо, пожалуй правильней будет command bus !== LSP

Ну раз никто не хочет вникать в суть вопроса, вопрос снимается. Спасибо за участие.
Это, наверное, из разряда "какой нафиг SOLID в PHP" ))
Аватара пользователя
ElisDN
Сообщения: 5841
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: command bus !== ООП?

Сообщение ElisDN »

Да, шина нарушает типизацию в плане разных исключений своих декораторов и разных результатов и исключений хэндлеров.

Так что если нужна полная типизация, то лучше обойтись без шины, напрямую иньектя хэндлер и доменный валидатор в контролер. Или валидатор в хэндлер.
Последний раз редактировалось ElisDN 2018.01.16, 10:33, всего редактировалось 1 раз.
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

Дима, ты изменил свое сообщение, а я только сейчас заметил. Мысли здравые, спасибо.
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: command bus !== ООП?

Сообщение noLogicOnlyWar »

Как можно избежать данных критических ограничений или, хотя бы, минимизировать последствия?
О каких последствиях речь?
Мы же понимаем что из app слоя прилетают либо sercvice либо domain исключения.
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

Обдумываю такой вариант.
Пусть будет интерфейс для шины, что бы можно было ее декорировать.
В контроллер инжектим ту шину, которая нужна, например ValidationCommandBus, но результат валидации будем возвращать в ответе шины.
В таком варианте сохраняется инвариант шины и постусловия не ослабляются, что соответствует канонам LSP.

Но как быть с исключениями домена, которые могут возникнуть в хендлере?
Обрабатывать их в хендлере? Или лучше пробрасывать и обрабатывать в контроллере?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: command bus !== ООП?

Сообщение zelenin »

Bio man писал(а): 2018.01.16, 11:23 Обдумываю такой вариант.
Пусть будет интерфейс для шины, что бы можно было ее декорировать.
В контроллер инжектим ту шину, которая нужна, например ValidationCommandBus, но результат валидации будем возвращать в ответе шины.
именно так и делаю - пускаю через всю ширу единый контекст, в который складываю то, что происходит внутри. Это похоже на attributes в psr-7 или context в golang.
Bio man писал(а): 2018.01.16, 11:23 Но как быть с исключениями домена, которые могут возникнуть в хендлере?
Обрабатывать их в хендлере?
да
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

noLogicOnlyWar писал(а): 2018.01.16, 11:19
Как можно избежать данных критических ограничений или, хотя бы, минимизировать последствия?
О каких последствиях речь?
Мы же понимаем что из app слоя прилетают либо sercvice либо domain исключения.
Последствия такие, что если работать с шиной по интерфейсу, то в наследниках нельзя выбрасывать исключения, которые интерфейс шины не подразумевает.
Доменные исключения, думаю, пробрасывать не будет ошибкой, хотя, не уверен.
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

Как-то так можно сделать.

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

class CommandResult { ... }

interface CommandBusInterface
{
    public function execute($command): CommandResult;
}

class SyncCommandBus implements CommandBusInterface
{
	public function execute($command): CommandResult { ... }
}

class ValidationCommandBus implements CommandBusInterface
{
	public function execute($command): CommandResult
	{ 
		if (!$validator->valid($command)) {
			return new CommandResult( ... );
		}
		
		...
	}
}

class Controller
{
	public function __construct(ValidationCommandBus $cb) { ... }
	
	public function actionExample()
	{
		$result = $this->cb->execute(new ExampleCommand( ... ));
		
		if ($result->has('validationErrors')) { ... }
		
		...
	}
}
Аватара пользователя
ElisDN
Сообщения: 5841
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: command bus !== ООП?

Сообщение ElisDN »

Bio man писал(а): 2018.01.16, 11:23 В контроллер инжектим ту шину, которая нужна, например ValidationCommandBus, но результат валидации будем возвращать в ответе шины.
Но если помимо ValidationBus вдекорируем какую-нибудь XxxBus или XxxMiddleware, то опять появятся ещё одни исключения и ещё одни результаты.

Шина - вещь динамическая. Принимающая любую object-команду и выполняющая любой callable-обработчик с любым mixed-результатом и любыми Throwable исключениями. Строгая типизация здесь не очень уместна.

И даже в случае с return $context->with('validation.errors', $errors) нужно знать о внутренностях и делать "утиную" проверку на $context->has('validation.errors').
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

ElisDN писал(а): 2018.01.16, 11:39
Bio man писал(а): 2018.01.16, 11:23 В контроллер инжектим ту шину, которая нужна, например ValidationCommandBus, но результат валидации будем возвращать в ответе шины.
Но если помимо ValidationBus вдекорируем какую-нибудь XxxBus или XxxMiddleware, то опять появятся ещё одни исключения и ещё одни результаты.

Шина - вещь динамическая. Принимающая любую object-команду и выполняющая любой callable-обработчик с любым mixed-результатом и любыми Throwable исключениями. Строгая типизация здесь не очень уместна.

И даже в случае с return $context->with('validation.errors', $errors) нужно знать о внутренностях и делать "утиную" проверку на $context->has('validation.errors').
Исключения не появятся, т.к. мы решили соблюдать LSP. А то, что результат стал "шире" это нормально. Тем более клиентский код (контроллер) знает, что используется ValidationBus, и, какого результата от него ожидать.
Производные классы не должны ослаблять постусловия (должны гарантировать, как минимум тоже, что и базовый класс).
О типизации я не говорю, я говорю о соблюдении контракта.
Насчет утиной проверки... Чем if отличается от try/catch?
Что мы сделаем

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

if ( $context->has('validation.errors')) {}
что

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

try {
  $bus->execute(...);
} catch (ValidationException) { ... }
Разница лишь в форме записи.
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: command bus !== ООП?

Сообщение zelenin »

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

Не надо использовать исключения как хитрый return.
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

zelenin писал(а): 2018.01.16, 11:54 в первом случае мы ожидаем увидеть ошибки, т.к. это нормальная ситуация, а во втором не ожидаем, т.к. это исключительная ситуация.

Не надо использовать исключения как хитрый return.
Полностью согласен.
Причем во 2 случае мы нарушаем контракт, т.к. об исключении валидации знает только шина валидации, но никак не интерфейс.
Поэтому, вариант с исключением отпадает сразу.
Аватара пользователя
ElisDN
Сообщения: 5841
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: command bus !== ООП?

Сообщение ElisDN »

Bio man писал(а): 2018.01.16, 11:50 О типизации я не говорю, я говорю о соблюдении контракта.
Насчет утиной проверки... Чем if отличается от try/catch?
Ничем не отличается.
От утиного "try {...} catch" или утиного "$result instanceof ErrorsCollection" пришли к утиному "isset($result['errors'])".
Подменили mixed throw на mixed return.
Bio man писал(а): 2018.01.16, 11:50 мы решили соблюдать LSP. А то, что результат стал "шире" это нормально.
Вместо этого у контракта можно сделать "шире" и исключения:

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

interface CommandBus
{
    /**
     * @param object $command
     * @throws \Throwable
     * @return mixed
     */
    public function handle($command)
}
чтобы теперь "об исключении валидации знал и интерфейс" для LSP. Это тоже нормально.
noLogicOnlyWar
Сообщения: 83
Зарегистрирован: 2017.07.04, 20:53

Re: command bus !== ООП?

Сообщение noLogicOnlyWar »

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

interface CommandBus
{
    /**
     * @param object $command
     * @throws \Throwable
     * @return mixed
     */
    public function handle($command)
}
чтобы теперь "об исключении валидации знал и интерфейс". Это тоже нормально.
Вот я тоже не понимаю, что мешает сделать так...
Причем клиентский код может обрабатывать \ServiceException|\DomainException обработка остальных ляжет на фреймворк.
Bio man
Сообщения: 609
Зарегистрирован: 2013.07.22, 10:40

Re: command bus !== ООП?

Сообщение Bio man »

хм. Да, можно и так. Разницы не вижу.
Похоже, золотой середины тут нет.

Склоняюсь все-таки отказаться от кастомных исключений в наследниках и возвращать все неким объектом-коллекцией (контекст).
И в контроллер инжектить по интерфейсу.
В таком случае, если шина валидации заменится на другую, то if ($context->has('validation.errors')) не приведет к побочным эффектам.
Ответить