我想知道我的方法的签名应该是什么,以便我优雅地处理不同类型的失败。
这个问题是我已经对 Scala 中的错误处理提出的许多问题的总结。你可以在这里找到一些问题:
目前,我了解以下内容:
- 两者都可以用作可能失败的方法调用的结果包装器
- Try 是偏右的 Either,失败是非致命的异常
- IO (scalaz) 有助于构建处理 IO 操作的纯方法
- 所有 3 个都可以在理解中轻松使用
- 由于不兼容的 flatMap 方法,所有 3 个都不容易在 for 理解中混合
- 在函数式语言中,我们通常不会抛出异常,除非它们是致命的
- 我们应该在非常特殊的情况下抛出异常。我想这是 Try 的方法
- 创建 Throwables 对 JVM 有性能成本,并且不打算用于业务流控制
存储层
现在请考虑我有一个UserRepository
. UserRepository
存储用户并定义findById
方法。可能会发生以下故障:
- 致命的失败(
OutOfMemoryError
) - IO 失败,因为数据库不可访问/不可读取
此外,用户可能会丢失,从而导致Option[User]
结果
使用存储库、SQL 的 JDBC 实现,可以抛出非致命异常(违反约束或其他),因此使用 Try 是有意义的。
当我们处理 IO 操作时,如果我们想要纯函数,那么 IO monad 也是有意义的。
所以结果类型可能是:
Try[Option[User]]
IO[Option[User]]
- 别的东西?
服务层
现在让我们介绍一个业务层,UserService
它提供了一些updateUserName(id,newUserName)
使用之前定义的方法findById
的存储库的方法。
可能会发生以下故障:
- 所有存储库故障都传播到服务层
- 业务错误:无法更新不存在的用户的用户名
- 业务错误:新用户名太短
那么结果类型可能是:
Try[Either[BusinessError,User]]
IO[Either[BusinessError,User]]
- 别的东西?
这里的 BusinessError 不是 Throwable,因为它不是异常失败。
使用理解
我想继续使用 for-comprehensions 来组合方法调用。
我们不能轻易地在理解上混合不同的 monad,所以我想我应该为我的所有操作使用某种统一的返回类型,对吧?
我只是想知道,在现实世界的 Scala 应用程序中,当可能发生不同类型的故障时,您如何成功地继续使用 for-comprehensions。
现在,为了理解对我来说效果很好,使用所有返回Either[Error,Result]
但所有不同类型的故障都融合在一起的服务和存储库,处理这些故障变得有点笨拙。
您是否定义了不同类型的 monad 之间的隐式转换以便能够用于理解?
您是否定义了自己的 monad 来处理故障?
顺便说一句,也许我很快就会使用异步 IO 驱动程序。所以我想我的返回类型可能更复杂:IO[Future[Either[BusinessError,User]]]
任何建议都会受到欢迎,因为我真的不知道该使用什么,而我的应用程序并不花哨:它只是一个 API,我应该能够区分可以显示给客户端的业务错误,以及技术错误。我试图找到一个优雅而纯粹的解决方案。