Я хотел бы знать, какая должна быть подпись моих методов, чтобы я обрабатывал различные виды сбоев элегантно.
Этот вопрос является как-то сводкой многих вопросов, которые я уже имел об обработке ошибок в Scala. Здесь вы можете найти несколько вопросов:
- Выбрасывание исключений в Scala, что такое "официальное правило"
- Либо, Параметры и для понимания
- Либо монадические операции
В настоящее время я понимаю следующее:
- Либо можно использовать в качестве оболочки результата для вызова метода, который может выйти из строя
- Попытка - это правильный биаизм. Либо, когда отказ является нефатальным исключением.
- IO (scalaz) помогает создавать чистые методы, которые обрабатывают операции ввода-вывода
- Все 3 легко можно использовать для понимания
- Все 3 не легко смешиваются в целях понимания из-за несовместимых методов FlatMap.
- В функциональных языках мы обычно не бросаем исключения, если они не являются фатальными.
- Мы должны бросить исключения для действительно исключительных условий. Я думаю, это подход Try.
- Создание Throwables имеет производительность для JVM и не предназначено для использования для управления бизнес-потоком.
Уровень репозитория
Теперь, пожалуйста, подумайте, что у меня есть UserRepository
. UserRepository
хранит пользователей и определяет метод findById
. Возможны следующие сбои:
- Фатальный сбой (
OutOfMemoryError
) - Ошибка ввода-вывода, поскольку база данных недоступна/доступна для чтения
Кроме того, пользователь может отсутствовать, что приведет к результату Option[User]
Используя реализацию репозитория JDBC, можно выбросить SQL, нефатальные исключения (нарушение ограничений или другие), чтобы иметь смысл использовать Try.
Поскольку мы имеем дело с операциями ввода-вывода, тогда монада IO также имеет смысл, если мы хотим чистых функций.
Таким образом, тип результата может быть:
-
Try[Option[User]]
-
IO[Option[User]]
- что-то еще?
Сервисный уровень
Теперь давайте представим бизнес-уровень UserService
, который предоставляет некоторый метод updateUserName(id,newUserName)
, который использует ранее определенный findById
репозитория.
Возможны следующие сбои:
- Все сбои репозитория распространяются на уровень службы
- Бизнес-ошибка: не удается обновить имя пользователя пользователя, который не существует
- Бизнес-ошибка: новое имя пользователя слишком короткое.
Тогда тип результата может быть:
-
Try[Either[BusinessError,User]]
-
IO[Either[BusinessError,User]]
- что-то еще?
BusinessError здесь не является Throwable, потому что это не исключительный сбой.
Использование понятий
Я бы хотел использовать методы for-comprehensions для объединения вызовов методов.
Мы не можем легко смешивать разные монады для понимания, поэтому, я думаю, у меня должен быть какой-то тип равномерного возвращения для всех моих операций?
Мне просто интересно, как вам добиться успеха в ваших реальных приложениях Scala, чтобы продолжать использовать для понимания, когда могут произойти различные сбои.
В настоящее время для понимания работает отлично для меня, используя службы и репозитории, которые все возвращают Either[Error,Result]
, но все разные виды сбоев растапливаются вместе, и это становится своего рода хаккой для обработки этих сбоев.
Вы определяете неявные преобразования между различными типами монад, чтобы иметь возможность использовать для-понимания?
Вы определяете свои собственные монады для обработки отказов?
Кстати, я скоро использую асинхронный драйвер ввода-вывода.
Поэтому, я думаю, мой тип возврата может быть еще сложнее: IO[Future[Either[BusinessError,User]]]
Любые советы приветствуются, потому что я действительно не знаю, что использовать, в то время как мое приложение не причудливо: это просто API, где я должен иметь возможность различать бизнес-ошибки, которые могут быть показаны клиенту сбоку и технические ошибки. Я пытаюсь найти элегантное и чистое решение.