Exceptions

Adelf

Administrator
Команда форума
Для упрощения допустим ситуацию, когда мы выделили некий Service layer, а Domain layer из него не выделили и все - и работа с базой и другой инфраструктурой и бизнес-логикой - все в одном там пока лежит.
Нужно нам иногда кидать исключения. Давайте пока самый просто юзкейс - publish the post. Можно делать все по канонам:
abstract class BusinessException;
class CantPublishAlreadyPublishedPost extends BusinessException;
class CantPublishPostWithEmptyBody extends BusinessException;

и метод PublishPostAction:: publish throws CantPublishAlreadyPublishedPost, CantPublishPostWithEmptyBody и еще полно всего типа база упала.

Вопроса два.
1) Если следовать принципу Information hiding то почему конкретно нельзя опубликовать пост - дело касается только PublishPostAction. Никто другой ничего конкретного из этой информации не извлечет. тогда не проще ли вообще:
final class BusinessException;
и генерить только new BusinessException("Post already published");
Поскольку только юзеру интересно почему конкретно не смог произойти экшен. Приложение интересно только - кто виноват. если юзер - пофиг. если мы(база упала), то надо залогировать.
Да и я помню некие дискуссии тут, что вещи типа "Post already published" даже не заслуживают эксепшена. можно просто вернуть некий "результат выполнения экшена" где опишем успешно ли и если нет, то error message.

2) Есть еще советы по оборачиванию всего в этом экшене в большой try и оборачивании всех эксепшенов в специальный ServiceLayer эксепшены. Потому что если эксепшен идет в другой слой, то его надо обернуть.
Вот как пример: https://pastebin.com/X4gjh7Dz
и CreateTestAction::create throws only CantCreateTestException и ничего больше.
Какая практическая польза от этого?

С выделением домена и его эксепшенами ситуация вроде несильно поменяется...
 

AnrDaemon

Продвинутый новичок
по канонам:
abstract class BusinessException;
implements FrameworkException, BusinessLogicException
Что именно ты собрался писать в абстрактном классе исключения?
и метод PublishPostAction:: publish throws CantPublishAlreadyPublishedPost, CantPublishPostWithEmptyBody и еще полно всего типа база упала.
Завари чайку и подумай, какое отношение "база упала" имеет к бизнес-логике?…
Бизнес-исключения будут кидать только бизнес-действия, и "база упала" сюда никак не влезает.
Так что https://gist.github.com/AnrDaemon/f4201775789912506d1e5030f8339241#file-init-main-php-L83-L109
 

Adelf

Administrator
Команда форума
Завари чайку и подумай, какое отношение "база упала" имеет к бизнес-логике?…
Мы просим наше приложение(уже выделенный сервисный слой) сделать какое-то действие.
Причиной что это действие не выполнено может стать и проблема с бд и что-то не так с логикой. первое мы обязаны залогировать и показать юзеру чтото типа "Oops, soory". Второе - просто показать юзеру в чем он неправ. и этот BusinessException - как раз чтобы выделять вторые от всех других.
Что такое FrameworkException - я не знаю :)
 

AnrDaemon

Продвинутый новичок
FrameworkException в моём случае это любое исключение, сгенерированное кодом моего приложения (i.e. в контекте неймспейса приложения).
Эти исключения по умолчанию не содержат компрометирующей информации, хотя и не всегда очевидны для пользователя.
Поскольку контроллер страницы, генерирующей собственно представление ошибки, получает эту самую ошибку на вход, он вполне может раскидать её ещё дальше по интерфейсам, чем это делается в обработчике, и вывести тот самый "Oopss" есть так нужно.
Главное, правильно маркировать ошибки, даже если у тебя слои в зачаточном состоянии.
 

WMix

герр M:)ller
Партнер клуба
если юзер - пофиг. если мы(база упала), то надо залогировать.
а точно уверен что юзер? показываешь список - артикул есть, вызываешь - артикула нет... (ошибка либо в списке либо в выборе)
 

Adelf

Administrator
Команда форума
а точно уверен что юзер? показываешь список - артикул есть, вызываешь - артикула нет... (ошибка либо в списке либо в выборе)
ну это может быть банально - другой юзер уже успел что-то поменять, а до OptimisticLockException мы не добрались(поскольку не стали пытаться сохранить).
Или просто UI тупой совсем и дает делать всякую чушь.
Возможно такие вещи стоит и логировать тоже. хз.
 

WMix

герр M:)ller
Партнер клуба
ну как ответ, я пишу, хотя чесно, тоже большого смысла не вижу кроме что в логе по Exception понимаешь мгновенно о чем эта ошибка.
а для клиента ответ на все одинаковый
PHP:
catch(BusinessException $e){
  http_response_code(400);
}
 

WMix

герр M:)ller
Партнер клуба
есть интересный момент в new BusinessException("Post already published");
а че на русском не будет? или всеже "Oopss"
 

флоппик

promotor fidei
Команда форума
Партнер клуба
Ждем @Вурдалак который расскажет, что бизнес-логика не должна кидать эксепшнов, что это чисто техническое уровня ошибок, и что "у нас в проекте, например" просто ты эмитишь эвент с состоянием "нельзя опубликовать пост повторно"
 

Вурдалак

Продвинутый новичок
1) Если следовать принципу Information hiding то почему конкретно нельзя опубликовать пост - дело касается только PublishPostAction
Странное утверждение. Почему я не могу сделать контракт таким образом (PostService::publish(): PostPublishingErrors), чтобы точно знать что произошло? Information hiding – это про свойства же.

Да и я помню некие дискуссии тут, что вещи типа "Post already published" даже не заслуживают эксепшена. можно просто вернуть некий "результат выполнения экшена" где опишем успешно ли и если нет, то error message.
Что такое «Post already published»?

class CantPublishPostWithEmptyBody extends BusinessException;
Я обычно рассматриваю такие вещи как \LogicException: если до этого дошло, то у меня баг в программе. Программа не должна доходить до этой точки, т.е. там должна быть какая-то клиентская валидация.

Ждем @Вурдалак который расскажет, что бизнес-логика не должна кидать эксепшнов, что это чисто техническое уровня ошибок, и что "у нас в проекте, например" просто ты эмитишь эвент с состоянием "нельзя опубликовать пост повторно"
Если это камень в мой огород, то я не понял претензии.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Смешались кони, люди ...

Предлагаю взять сервисную архитектуру, и рассмотреть как все это делается, если между слоями API, и станет понятнее, как это сделать в монолите.
Для обработки постов нам нужны:
* сервис аутентификации-авторизации
* middleware для API
* база для хранения постов
* backend для обсчетов и служебных задач
* сервис рассылок

Никакого AlreadyPublishedPost в природе не существует (Вурдалак +1).
Есть запись в базе, которую можно создать и обновить. Есть API, которому можно соответствовать или не соответствовать. Есть ошибки аутентификации - пароль не подошел, авторизации - сессия истекла.

Если запрос не соответствует API - это 400я ошибка протокола. Надо ли это обрабатывать? Для статистики и антиспама может быть создан специальный сервис. Допустим, что его нет.

Кому middleware может бросать исключение на несоответствие запроса протоколу? Клиенту нужен только 400й. Никому. Никаких BusinessExceptions не будет - их некому обрабатывать. Это сказочная сущность, которая может паразитировать только в монолите.

Если база недоступна - это надо чинить. Это 500й ответ, и сообщение админу. Кому можно бросить исключение о сбое связи с базой? Сервису рассылки уведомлений. Он специально сидит ждет сообщение о проблеме.

Вот и все.
 
Последнее редактирование:

Adelf

Administrator
Команда форума
Странное утверждение. Почему я не могу сделать контракт таким образом (PostService::publish(): PostPublishingErrors), чтобы точно знать что произошло? Information hiding – это про свойства же.
Вроде и не про свойства тоже. Вот кому, кроме конечного юзера, может быть интересно разбирать PostPublishingErrors? Вроде никому.

Что такое «Post already published»?
Ну это самый простой вариант того, что внутреннее состояние объекта не позволяет произвести действие. Чуть более сложный вариант - попытаться оплатить отмененный заказ.

Я обычно рассматриваю такие вещи как \LogicException: если до этого дошло, то у меня баг в программе. Программа не должна доходить до этой точки, т.е. там должна быть какая-то клиентская валидация.
Ну какая разница как это называть. Да, я примерно о том же. Есть UI и он не должен позволять производить неправильные действия. Например ту же оплату отмененного заказа. Но есть же и публичные API. И там надо корректно отрабатывать все эти ошибки. Так что не совсем LogicException.

Никакого AlreadyPublishedPost в природе не существует (Вурдалак +1).
Есть запись в базе, которую можно создать и обновить. Есть API, которому можно соответствовать или не соответствовать. Есть ошибки аутентификации - пароль не подошел, авторизации - сессия истекла.

Если запрос не соответствует API - это 400я ошибка протокола. Надо ли это обрабатывать? Для статистики и антиспама может быть создан специальный сервис. Допустим, что его нет.

Кому middleware может бросать исключение на несоответствие запроса протоколу? Клиенту нужен только 400й. Никому. Никаких BusinessExceptions не будет - их некому обрабатывать. Это сказочная сущность, которая может паразитировать только в монолите.
А как нужно генерировать эту 400 ошибку? Я вот генерирую этим самым BusinessException. Точнее ловлю его в глобальном отловителе эксепшенов и отдаю нужный response с 4хх кодом.
 

grigori

( ͡° ͜ʖ ͡°)
Команда форума
Чуть более сложный вариант - попытаться оплатить отмененный заказ.
как проектировщик ecommerce-системы, я не могу читать дальше
ощущение в стиле "стою на асфальте я в лыжи обутый". Попытаться. Оплатить. Омененный заказ?

1. Нельзя пытаться оплатить заказ - можно или оплатить, или не оплатить.Есть такой термин - "транзакция".
2. Заказ на сайте, оплата - в шлюзе. Это как билет и поезд. Поезд едет, билет покупают. В огороде бузина, в городе дядька. Независимые процессы.
3. Ты хоть раз отменял заказ? Например, на Амазоне. Ты офорляешь заказ, вводишь номер карты. Твой заказ сначала собирают на складе, потом идет процессинг твоей карты - на следующий день после оформления обычно. Если процессинг проходит - посылка передается на почту. Если ты его отменил когда задача ушла в процессинг карты - что будет? Товар отправляют в цех работы с возвратом, деньги возвращают на карту.
4. Заказ можно отменить в момент получения товара или вернуть товар, который не подошел - транзакция точно так же отменяется.

Давай не придумывать абстрактных коней в вакууме, описывай реальную ситуацию.
 

Adelf

Administrator
Команда форума
@grigori, ой да какая разница. ты такой неабстрактный :)
Ну давай попытаемся отменить заказ, который уже отменен. через API.
Оно нам должно выдать некую ошибку, что нельзя так.
 
Сверху