【问题标题】:Service class and logging design Scala with Futures带有 Futures 的服务类和日志设计 Scala
【发布时间】:2018-09-25 10:59:03
【问题描述】:

我有一个从数据库中获取一些数据的服务类(对于上下文,我使用的是 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]

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

但我有以下问题:

  1. 我的ErrorType 应该扩展Exception,还是应该只使用密封特征样式并从中扩展。我听说在 Scala 中使用异常不是一个好习惯,所以我不确定正确的方法是什么。

  2. 如何处理日志而不用过多的日志语句污染控制器类?如果一个控制器类调用了一堆这样的服务方法,控制器将不得不在 for comprehension 中处理几个不同的ErrorTypes。假设我使用?| 将所有单子提升到包装单子,我想避免这种情况:

    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 => ...
                 }
    

谢谢!

【问题讨论】:

    标签: scala playframework-2.0 scalaz


    【解决方案1】:

    我认为 Future[ErrorType / String] 有点矫枉过正,因为 Future[T] 已经可以保存 T 类型的对象或异常派生的对象(请参阅 Future.successful(...)/Future.failed(. ..))

    我的 ErrorType 应该扩展 Exception,还是应该只使用密封 trait 样式并从中扩展。我听说在 Scala 中使用异常不是一个好习惯,所以我不确定正确的方法是什么。

    我建议使用一个类(或一组类,每个特定错误类型一个),例如从 Exception 派生的 YourAppException,因为无论如何您都需要以一种或另一种方式处理低级异常。

    我同意抛出/捕获异常不适用于函数式代码,最好使用 Try[T] 或 Future[T] 以更明确的方式返回错误。另一方面,使用 Exception 派生类来保存一些错误信息并没有错。将原始的非应用程序(比如 IO)异常包装在应用程序中并在异常的“原因”中保留对初始异常的引用以进行故障排除通常很有用。它提供了一个提供更多上下文特定错误消息的机会。

    如何处理日志而不用过多的日志语句污染控制器类?

    考虑在 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))
             }
    

    【讨论】:

      【解决方案2】:

      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] 时,您会丢失信息,因此您应该记录它。 当您从 Either[MyError, String] 中提取 String 时,也会发生同样的情况。

      【讨论】:

        猜你喜欢
        • 2023-04-07
        • 2014-10-26
        • 1970-01-01
        • 2021-11-21
        • 2016-05-31
        • 2023-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多