服务类和日志设计Scala与期货

时间:2018-04-15 17:31:36

标签: scala playframework-2.0 scalaz

我有一个服务类从数据库中获取一些数据(对于上下文,我使用Play!Framework)。这是一个示例方法:

  def getAccessToken(id: BSONObjectID): Future[Option[String]] = {
    userDAO.find(id).map {
      case Some(user) =>
        user.settings flatMap (_.accessToken)
      case _ => None
    }
  }

我试图在这件事情上改进错误处理(Scala的新功能),因为有些事情可能会出错:

  1. 可能找不到用户
  2. 可能会找到用户,但可能未设置accessTokenaccessTokenOption[String]
  3. 就目前而言,我无法区分这两者。我解决这个问题的自然倾向是使用Scalaz中的\/并且返回类型为Future[ErrorType \/ String],这似乎是一种合理的方法。在我的控制器方法中,我可以通过提升到包装器monad来理解一堆不同的服务方法。

    但我有以下问题:

    1. 我的ErrorType是否应该延伸Exception,或者我应该使用密封的特征样式并从中延伸。我听说在Scala中使用异常并不是一个好习惯,因此我不确定正确的方法是什么。

    2. 如何在不使用过多日志语句污染控制器类的情况下处理日志记录?如果控制器类调用了许多这些服务方法,则控制器必须在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 => ...
                   }
      
    3. 谢谢!

2 个答案:

答案 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]时会发生同样的情况。