command bus !== ООП?
command bus !== ООП?
Видел на форуме пример ValidationCommandBus, который бросает исключение валидации. Так вот, это прямое нарушение LSP. Получается, что контракт базового класса (интерфейса) не соблюдается, т.к. интерфейс должен четко регламентировать, какие исключения могут быть обработаны. Отсюда следует, что класс использующий CommandBusInterface должен знать о деталях конкретной реализации (ValidationCommandBus), от чего целесообразность использования интерфейса теряется.
http://sergeyteplyakov.blogspot.com/201 ... ciple.html
Я уже не говорю о доменных исключениях, которые могут вылететь за пределы шины. Хотя, доменные исключения можно отловить в обработчике и вернуть из шины некий объект результата с описанием ошибки. Лишь бы интерфейс шины предполагал такой инвариант.
Как можно избежать данных критических ограничений или, хотя бы, минимизировать последствия?
Обсуждаем)
http://sergeyteplyakov.blogspot.com/201 ... ciple.html
Я уже не говорю о доменных исключениях, которые могут вылететь за пределы шины. Хотя, доменные исключения можно отловить в обработчике и вернуть из шины некий объект результата с описанием ошибки. Лишь бы интерфейс шины предполагал такой инвариант.
Как можно избежать данных критических ограничений или, хотя бы, минимизировать последствия?
Обсуждаем)
Re: command bus !== ООП?
На чём основывается это утверждение?интерфейс должен четко регламентировать, какие исключения могут быть обработаны
Re: command bus !== ООП?
На соблюдении контракта. По ссылочке почитай.
Я не говорю о runtime exception, который может вылететь от куда угодно. Но исключения, которые выбрасывает шина, должны быть задукоментированы, т.е. должны быть частью контракта.
Если мы работаем с интерфейсом и используем реализацию, которая бросает исключение, которое не указано в контракте, то мы вынуждены знать о деталях наследника, что бы обработать исключение (например, исключение валидации).
Последний раз редактировалось Bio man 2018.01.16, 10:24, всего редактировалось 1 раз.
Re: command bus !== ООП?
...
Последний раз редактировалось ElisDN 2018.01.16, 10:30, всего редактировалось 2 раза.
Re: command bus !== ООП?
Причем здесь ООП, если дальше вы пишете про LSP?command bus !== ООП.
Исключения должны кидаться при исключительных, непредусматриваемых кодом, ситуациях. Валидация - это штатная функция. Она должна возвращать результат валидации.
Re: command bus !== ООП?
Да, шина нарушает типизацию в плане разных исключений своих декораторов и разных результатов и исключений хэндлеров.
Так что если нужна полная типизация, то лучше обойтись без шины, напрямую иньектя хэндлер и доменный валидатор в контролер. Или валидатор в хэндлер.
Так что если нужна полная типизация, то лучше обойтись без шины, напрямую иньектя хэндлер и доменный валидатор в контролер. Или валидатор в хэндлер.
Последний раз редактировалось ElisDN 2018.01.16, 10:33, всего редактировалось 1 раз.
Re: command bus !== ООП?
Дима, ты изменил свое сообщение, а я только сейчас заметил. Мысли здравые, спасибо.
-
- Сообщения: 83
- Зарегистрирован: 2017.07.04, 20:53
Re: command bus !== ООП?
О каких последствиях речь?Как можно избежать данных критических ограничений или, хотя бы, минимизировать последствия?
Мы же понимаем что из app слоя прилетают либо sercvice либо domain исключения.
Re: command bus !== ООП?
Обдумываю такой вариант.
Пусть будет интерфейс для шины, что бы можно было ее декорировать.
В контроллер инжектим ту шину, которая нужна, например ValidationCommandBus, но результат валидации будем возвращать в ответе шины.
В таком варианте сохраняется инвариант шины и постусловия не ослабляются, что соответствует канонам LSP.
Но как быть с исключениями домена, которые могут возникнуть в хендлере?
Обрабатывать их в хендлере? Или лучше пробрасывать и обрабатывать в контроллере?
Пусть будет интерфейс для шины, что бы можно было ее декорировать.
В контроллер инжектим ту шину, которая нужна, например ValidationCommandBus, но результат валидации будем возвращать в ответе шины.
В таком варианте сохраняется инвариант шины и постусловия не ослабляются, что соответствует канонам LSP.
Но как быть с исключениями домена, которые могут возникнуть в хендлере?
Обрабатывать их в хендлере? Или лучше пробрасывать и обрабатывать в контроллере?
Re: command bus !== ООП?
именно так и делаю - пускаю через всю ширу единый контекст, в который складываю то, что происходит внутри. Это похоже на attributes в psr-7 или context в golang.
да
Re: command bus !== ООП?
Последствия такие, что если работать с шиной по интерфейсу, то в наследниках нельзя выбрасывать исключения, которые интерфейс шины не подразумевает.noLogicOnlyWar писал(а): ↑2018.01.16, 11:19О каких последствиях речь?Как можно избежать данных критических ограничений или, хотя бы, минимизировать последствия?
Мы же понимаем что из app слоя прилетают либо sercvice либо domain исключения.
Доменные исключения, думаю, пробрасывать не будет ошибкой, хотя, не уверен.
Re: command bus !== ООП?
Как-то так можно сделать.
Код: Выделить всё
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')) { ... }
...
}
}
Re: command bus !== ООП?
Но если помимо ValidationBus вдекорируем какую-нибудь XxxBus или XxxMiddleware, то опять появятся ещё одни исключения и ещё одни результаты.
Шина - вещь динамическая. Принимающая любую object-команду и выполняющая любой callable-обработчик с любым mixed-результатом и любыми Throwable исключениями. Строгая типизация здесь не очень уместна.
И даже в случае с return $context->with('validation.errors', $errors) нужно знать о внутренностях и делать "утиную" проверку на $context->has('validation.errors').
Re: command bus !== ООП?
Исключения не появятся, т.к. мы решили соблюдать LSP. А то, что результат стал "шире" это нормально. Тем более клиентский код (контроллер) знает, что используется ValidationBus, и, какого результата от него ожидать.ElisDN писал(а): ↑2018.01.16, 11:39Но если помимо ValidationBus вдекорируем какую-нибудь XxxBus или XxxMiddleware, то опять появятся ещё одни исключения и ещё одни результаты.
Шина - вещь динамическая. Принимающая любую object-команду и выполняющая любой callable-обработчик с любым mixed-результатом и любыми Throwable исключениями. Строгая типизация здесь не очень уместна.
И даже в случае с return $context->with('validation.errors', $errors) нужно знать о внутренностях и делать "утиную" проверку на $context->has('validation.errors').
О типизации я не говорю, я говорю о соблюдении контракта.Производные классы не должны ослаблять постусловия (должны гарантировать, как минимум тоже, что и базовый класс).
Насчет утиной проверки... Чем if отличается от try/catch?
Что мы сделаем
Код: Выделить всё
if ( $context->has('validation.errors')) {}
Код: Выделить всё
try {
$bus->execute(...);
} catch (ValidationException) { ... }
Re: command bus !== ООП?
в первом случае мы ожидаем увидеть ошибки, т.к. это нормальная ситуация, а во втором не ожидаем, т.к. это исключительная ситуация.
Не надо использовать исключения как хитрый return.
Не надо использовать исключения как хитрый return.
Re: command bus !== ООП?
Полностью согласен.
Причем во 2 случае мы нарушаем контракт, т.к. об исключении валидации знает только шина валидации, но никак не интерфейс.
Поэтому, вариант с исключением отпадает сразу.
Re: command bus !== ООП?
Ничем не отличается.
От утиного "try {...} catch" или утиного "$result instanceof ErrorsCollection" пришли к утиному "isset($result['errors'])".
Подменили mixed throw на mixed return.
Вместо этого у контракта можно сделать "шире" и исключения:
Код: Выделить всё
interface CommandBus
{
/**
* @param object $command
* @throws \Throwable
* @return mixed
*/
public function handle($command)
}
-
- Сообщения: 83
- Зарегистрирован: 2017.07.04, 20:53
Re: command bus !== ООП?
Вот я тоже не понимаю, что мешает сделать так...чтобы теперь "об исключении валидации знал и интерфейс". Это тоже нормально.Код: Выделить всё
interface CommandBus { /** * @param object $command * @throws \Throwable * @return mixed */ public function handle($command) }
Причем клиентский код может обрабатывать \ServiceException|\DomainException обработка остальных ляжет на фреймворк.
Re: command bus !== ООП?
хм. Да, можно и так. Разницы не вижу.
Похоже, золотой середины тут нет.
Склоняюсь все-таки отказаться от кастомных исключений в наследниках и возвращать все неким объектом-коллекцией (контекст).
И в контроллер инжектить по интерфейсу.
В таком случае, если шина валидации заменится на другую, то if ($context->has('validation.errors')) не приведет к побочным эффектам.
Похоже, золотой середины тут нет.
Склоняюсь все-таки отказаться от кастомных исключений в наследниках и возвращать все неким объектом-коллекцией (контекст).
И в контроллер инжектить по интерфейсу.
В таком случае, если шина валидации заменится на другую, то if ($context->has('validation.errors')) не приведет к побочным эффектам.