带有Futures的Scala服务类和日志设计

带有Futures的Scala服务类和日志设计,scala,playframework-2.0,scalaz,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

我有一个服务类,它从数据库中获取一些数据(对于上下文,我使用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
    是一个
    选项[String]
  • 就目前情况而言,我无法区分两者。我自然倾向于使用Scalaz的
    \/
    ,并将返回类型设置为
    Future[ErrorType\/String]
    ,这似乎是一种合理的方法。在我的控制器方法中,我可以通过提升到包装器monad中来理解一系列不同的服务方法

    但我有以下问题:

  • 我的
    ErrorType
    应该扩展
    Exception
    ,还是应该使用sealed-trait样式并从中扩展。我听说在Scala中使用异常不是一个好的实践,所以我不确定什么是正确的方法

  • 如何处理日志记录而不使用过多的日志语句污染控制器类?如果一个控制器类调用了一组这些服务方法,那么控制器必须在中处理多个不同的
    ErrorType
    s以供理解。假设我使用
    ?|
    将所有单子提升到包装器单子,我希望避免这种情况:

    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 => ...
                 }
    
    accessToken错误匹配{case error 1=>
    记录器错误(
    找不到用户的访问令牌:“+id”
    .toString())
    内部服务器错误(
    ApiResponse(“内部服务器错误”,
    “无法获取令牌。”))
    案例错误2=>。。。
    }
    

  • 谢谢

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

    我的ErrorType应该扩展异常,还是应该使用sealed-trait样式并从此扩展。我听说在Scala中使用异常不是一个好的实践,所以我不确定什么是正确的方法

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

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

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

    考虑将错误消息封装在异常派生的案例类中,表示应用程序错误,以便可以使用Exception.getMessage统一访问错误消息。在AppException中添加一些方法来构造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))
             }
    
    def getAccessToken(id:BSONObjectID):未来[String]={
    userDAO.find(id).flatMap{
    案例部分(用户)=>
    val optoken=user.settings.flatMap(ux.accessToken)
    optoken.map(Future.successful).getOrElse(Future.failed(AccessTokenIsInvalid(用户)))
    case=>Future.failed(UserNotFoundError(user))
    }
    }
    案例类AccessTokenIsInvalid(用户:字符串)
    扩展YourAppException(s“访问令牌对用户$user无效”){
    }
    accessToken
    logger.error(error.getMessage)
    内部服务器错误(
    ApiResponse(“内部服务器错误”,error.getMessage))
    }
    
    我认为Future[ErrorType/String]有点过分,因为Future[T]已经可以保存类型为T的对象或异常派生的对象(请参见Future.successful(…)/Future.failed(…)

    我的ErrorType应该扩展异常,还是应该使用sealed-trait样式并从此扩展。我听说在Scala中使用异常不是一个好的实践,所以我不确定什么是正确的方法

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

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

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

    考虑将错误消息封装在异常派生的案例类中,表示应用程序错误,以便可以使用Exception.getMessage统一访问错误消息。在AppException中添加一些方法来构造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))
             }
    
    def getAccessToken(id:BSONObjectID):未来[String]={
    userDAO.find(id).flatMap{
    案例部分(用户)=>
    val optoken=user.settings.flatMap(ux.accessToken)
    optoken.map(Future.successful).getOrElse(Future.failed(AccessTokenIsInvalid(用户)))
    案例=>Future.failed(UserNotFoundError(u