微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

scala – Play Framework:如何实现正确的错误处理

我有一个带有几个模块的Play应用程序,每个模块都有自己的异常集.以下是三个例子:

模块常见:

package services.common

trait CommonErrors {

  final case class NotFound(id: String) extends Exception(s"object $id not found")
  final case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
  ...

  // `toJson` is just an extension method that converts an exception to JSON
  def toResult(e: Exception): Result = e match {
    case NotFound => Results.NotFound(e.toJson)
    case InvalidId => Results.BadRequest(e.toJson)
    case _ => Results.InternalError(e.toJson)
  }
}

模块认证:

package services.auth

trait AuthErrors {

  final case class UserNotFound(e: NotFound) extends Exception(s"user ${e.id} not found")
  final case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
  ...

  // `toJson` is just an extension method that converts an exception to JSON
  def toResult(e: Exception): Result = e match {
    case UserNotFound => Results.NotFound(e.toJson)
    case UserAlreadyExists => Results.BadRequest(e.toJson)
    case _ => Results.InternalError(e.toJson)
  }
}

模块其他:

trait OtherErrors {

  final case class AnotherError(s: String) extends Exception(s"another error: $s")
  ...

  // `toJson` is just an extension method that converts an exception to JSON
  def toResult(e: Exception): Result = e match {
    case AnotherError => Results.BadRequest(e.toJson)
    ...
    case _ => Results.InternalError(e.toJson)
  }
}

如您所见,每个特征定义了一组异常,并提供了将该异常转换为JSON响应的方法,如下所示:

{
  "status": 404,"code": "not_found","message": "user 123456789123456789123456 not found","request": "https://myhost.com/users/123456789123456789123456"
}

我想要实现的是让每个模块定义它的异常,重用公共模块中定义的异常,并根据需要混合异常特征:

object Users extends Controller {

  val errors = new CommonErrors with AuthErrors with OtherErrors {
    // here I have to override `toResult` to make the compiler happy
    override def toResult(e: Exception) = super.toResult
  }

  def find(id: String) = Action { request =>
    userService.find(id).map { user =>
      Ok(success(user.toJson))
    }.recover { case e =>
      errors.toResult(e) // this returns the appropriate result
    }
  }
}

如果你看看我是如何重写toResult的,我总是返回super.toResult,它对应于trait OtherErrors中包含的实现……这个实现可能会遗漏一些预期在CommonErrors.toResult中找到的模式.

我肯定错过了一些东西……所以问题是:用toResult的多个实现解决问题的设计模式是什么?

解决方法

你可以使用 Stackable Trait模式.为简化起见,我会用.getMessage替换你的.toJson:

定义基本特征:

trait ErroRSStack {
  def toResult(e: Exception): Result = e match {
    case _ => Results.InternalServerError(e.getMessage)
  }
}

和可堆叠的特征:

trait CommonErrors extends ErroRSStack {
  case class NotFound(id: String) extends Exception(s"object $id not found")
  case class InvalidId(id: String) extends Exception(s"$id is an invalid id")

  override def toResult(e: Exception): Result = e match {
    case e: NotFound => Results.NotFound(e.getMessage)
    case e: InvalidId => Results.BadRequest(e.getMessage)
    case _ => super.toResult(e)
  }
}

trait AuthErrors extends ErroRSStack {
  case class UserNotFound(id: String) extends Exception(s"user $id not found")
  case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")

  override def toResult(e: Exception): Result = e match {
    case e: UserNotFound => Results.NotFound(e.getMessage)
    case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
    case _ => super.toResult(e)
  }
}

trait OtherErrors extends ErroRSStack {    
  case class AnotherError(s: String) extends Exception(s"another error: $s")

  override def toResult(e: Exception): Result = e match {
    case e: AnotherError => Results.BadRequest(e.getMessage)

    case _ => super.toResult(e)
  }
}

所以,如果我们有一些堆栈

val errors = new CommonErrors with AuthErrors with OtherErrors

并定义了一些帮手

import java.nio.charset.StandardCharsets.UTF_8
import play.api.libs.iteratee.Iteratee
import concurrent.duration._
import scala.concurrent.Await

def getResult(ex: Exception) = {
  val res = errors.toResult(ex)
  val body = new String(Await.result(res.body.run(Iteratee.consume()),5 seconds),UTF_8)
  (res.header.status,body)
}

以下代码

import java.security.GeneralSecurityException

getResult(errors.UserNotFound("Riddle"))
getResult(errors.UserAlreadyExists("Weasley"))
getResult(errors.NotFound("Gryffindor sword"))
getResult(errors.AnotherError("Snape's death"))
getResult(new GeneralSecurityException("Marauders's map"))

会产生合理的产量

res0: (Int,String) = (404,user Riddle not found)
res1: (Int,String) = (400,user identified by Weasley already exists)
res2: (Int,object Gryffindor sword not found)
res3: (Int,another error: Snape's death)
res4: (Int,String) = (500,Marauders's map)

我们也可以重构这段代码,拉出从特征中归类的案例,并使函数更具组合性:

type Resolver = PartialFunction[Exception,Result]

object ErroRSStack {
  val resolver: Resolver = {
    case e => Results.InternalServerError(e.getMessage)
  }
}

trait ErroRSStack {
  def toResult: Resolver = ErroRSStack.resolver
}

object CommonErrors {
  case class NotFound(id: String) extends Exception(s"object $id not found")
  case class InvalidId(id: String) extends Exception(s"$id is an invalid id")
  val resolver: Resolver = {
    case e: NotFound => Results.NotFound(e.getMessage)
    case e: InvalidId => Results.BadRequest(e.getMessage)
  }
}

trait CommonErrors extends ErroRSStack {
  override def toResult = CommonErrors.resolver orElse super.toResult
}

object AuthErrors {
  case class UserNotFound(id: String) extends Exception(s"user $id not found")
  case class UserAlreadyExists(email: String) extends Exception(s"user identified by $email already exists")
  val resolver: Resolver = {
    case e: UserNotFound => Results.NotFound(e.getMessage)
    case e: UserAlreadyExists => Results.BadRequest(e.getMessage)
  }
}

trait AuthErrors extends ErroRSStack {
  override def toResult = AuthErrors.resolver orElse super.toResult
}

object OtherErrors {
  case class AnotherError(s: String) extends Exception(s"another error: $s")

  val resolver: Resolver = {
    case e: AnotherError => Results.BadRequest(e.getMessage)
  }
}

trait OtherErrors extends ErroRSStack {
  override def toResult = OtherErrors.resolver orElse super.toResult
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐