创建和引发异常是一项昂贵的工作……而且在Web应用程序中,根本不需要抛出异常。如果在控制器使用的服务类中发生错误,则抛出异常是有道理的……但是,如果控制器已经意识到问题(例如,未找到用户),则可以返回描述问题的JSON 。
解决方案可以是仅在必要时Error
扩展的案例类Throwable
,即,当控制器调用的服务失败时:
object MyErrors {
trait Error { def getMessage: String }
final case class UserNotFound(userId: Strig) extends Error { def getMessage = s"user $userId not found" }
final case class UserNotFoundException(userId: String) extends Throwable (s"user $userId not found") with Error
final case class DuplicateKey(key: Strig) extends Error { def getMessage = s"key $key already exists" }
final case class DuplicateKeyException(key: String) extends Throwable (s"key $key already exists") with Error
...
}
无论是否Error
为Throwable
,我都可以处理以下可能的错误:
object Users extends Controller {
...
def find(id: String) = Action { request =>
userService.find(id).map {
case Some(user) => Ok(success(user.toJson))
case None =>
// UserNotFound implements Error but does not inherits from Throwable
errors.toResult(UserNotFound(id))
}.recover { case e =>
// e is thrown by userService.find() and extends Throwable and implements Error (e.g. DuplicateKeyException)
errors.toResult(e)
}
}
}
errors.toResult
地图当前异常或错误,以适当的HTTP结果(例如BadRequest
,NotFound
等),并转换e
到JSON -看到这个帖子的完整实现。
现在我的问题是:有没有那么复杂的方法可以做到这一点?如您所见,我必须为一个错误创建两个不同的案例类(在示例中,我为使事情简单起见,重复了两次错误消息)...
两种不同的想法:
如果异常的开销与堆栈跟踪有关,但是如果异常开销不那么高,您可能要使用异常,那么有一个用于异常的mixin将跳过在堆栈跟踪中生成 scala.util.control.NoStackTrace
如果更多是关于编写干净的FP代码(异常不适合且返回类型中包含所有可能的结果),则可以使用scala.util.Either
(或减少冗长,例如,Or
从scalactic库或\/
ScalaZ中获取)。
与Either
您一起将Left
视为失败和Right
成功。然后有方便的方法可以在Option
和之间进行转换Either
。
val opt: Option[Int] = None
val either: Either[String, Int] = opt.toRight("Value for none/left")
如果您使用right
投影,则您甚至可以进行理解,如果值不是,则会早退Right
。
val goodOrBad1: Either[String, Int] = Right(5)
val goodOrBad2: Either[String, Int] = Left("Bad")
val result: Either[String, Int] = for {
good1 <- goodOrBad1.right
good2 <- goodOrBad2.right
} yield good1 + good2
// fails with first non Right
result == Left("Bad")
让我们假装userService.find
并cartService.find
返回Option[Something]
,这意味着您可以执行以下操作:
def showCart(id: String) = Action { request =>
val userAndCartOrError: Either[Error, (User, Cart)] = for {
user <- userService.find(id).toRight(UserNotFound(id)).right
cart <- cartService.findForUser(id).toRight(NoCart(id)).right
} yield (user, cart)
userAndCartOrError.fold(
error => Errors.toResult(error),
userAndCart => Ok(Json.toJson(userAndCart))
)
}
当然,对于期货来说,它变得更加混乱,因为您不能将不同的monad混入同一理解中(Future
和 Either
)
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句