我有一个服务类从数据库中获取一些数据(对于上下文,我使用Play!Framework)。这是一个示例方法:
def getAccessToken(id: BSONObjectID): Future[Option[String]] = {
userDAO.find(id).map {
case Some(user) =>
user.settings flatMap (_.accessToken)
case _ => None
}
}
我试图在这件事情上改进错误处理(Scala的新功能),因为有些事情可能会出错:
accessToken
(accessToken
是Option[String]
)就目前而言,我无法区分这两者。我解决这个问题的自然倾向是使用Scalaz中的\/
并且返回类型为Future[ErrorType \/ String]
,这似乎是一种合理的方法。在我的控制器方法中,我可以通过提升到包装器monad来理解一堆不同的服务方法。
但我有以下问题:
我的ErrorType
是否应该延伸Exception
,或者我应该使用密封的特征样式并从中延伸。我听说在Scala中使用异常并不是一个好习惯,因此我不确定正确的方法是什么。
如何在不使用过多日志语句污染控制器类的情况下处理日志记录?如果控制器类调用了许多这些服务方法,则控制器必须在for comprehension中处理几个不同的ErrorType
。假设我将所有monad解除为?|
的包装器monad,我想避免这种情况:
accessToken <- service.getAccessToken(id) ?| { error => error match { case Error1 =>
logger.error(
"Unable to find access token for user: " + id
.toString())
InternalServerError(
ApiResponse("internal_server_error",
"Unable to get token."))
case Error2 => ...
}
谢谢!
答案 0 :(得分:1)
我认为Future [ErrorType / String]有点矫枉过正,因为Future [T]已经可以拥有T类型的对象或者Exception派生的对象(参见Future.successful(...)/ Future.failed(。 ..))
我的ErrorType是否应该扩展Exception,或者我应该只使用密封的trait样式并从中扩展。我听说在Scala中使用异常不是好习惯,所以我不确定正确的方法是什么。
我建议使用一个类(或一组类,每个特定的错误类型一个),比如从Exception派生的YourAppException,因为你需要以某种方式处理低级异常。
我同意抛出/捕捉异常与功能代码不太一致,最好使用Try [T]或Future [T]以更明确的方式返回错误。另一方面,使用Exception派生类来保存一些错误信息没有任何问题。将原始的非应用程序(例如IO)异常包装在应用程序中通常很有用,并在Exception的“原因”中保留对初始异常的引用以进行故障排除。它提供了一个提供更多特定于上下文的错误消息的机会。
如何在不使用过多日志语句污染控制器类的情况下处理日志记录?
考虑在Exception派生的案例类中封装错误消息,表示应用程序错误,以便您可以使用exception.getMessage统一访问错误消息。很容易为YourAppException添加一些方法来构造ApiResponse。
def getAccessToken(id: BSONObjectID): Future[String] = {
userDAO.find(id).flatMap {
case Some(user) =>
val optToken = user.settings.flatMap (_.accessToken)
optToken.map(Future.successful).getOrElse(Future.failed(AccessTokenIsInvalid(user)))
case _ => Future.failed(UserNotFoundError(user))
}
}
case class AccessTokenIsInvalid(user: String)
extends YourAppException(s"Access token is invalid for user $user") {
}
accessToken <- service.getAccessToken(id) ?| { error =>
logger.error(error.getMessage)
InternalServerError(
ApiResponse("internal_server_error", error.getMessage))
}
答案 1 :(得分:1)
1)是的,你是正确的。异常的问题在于,当某些内容失败时,很难对其进行模式匹配以检测原因。 我会那样做:
sealed trait MyError
object UserNotFound extends MyError
object AuthFailed extends MyError
type MyResult = Either[MyError, String]
2)如果程序是良好类型的,则在丢失信息的地方需要进行日志记录。
例如,如果您处理val x = Future[Either[Error, String]]
,那么您还没有限制潜在的错误,因此日志记录是可选的。
但是当你以某种方式尝试从中提取Either[MyError, String]
时,你会丢失信息,因此你应该记录它。
从String
中提取Either[MyError, String]
时会发生同样的情况。