播放框架:如何实现正确的错误处理 [英] Play Framework: How to implement proper error handling
问题描述
模块 common
:
package services.common
trait CommonErrors {
final case class NotFound(id:String)extends Exception(sobject $ id没有找到)
final case class InvalidId(id:String)extends Exception(s$ id is a invalid id)
...
//`toJson`只是一个扩展方法,将异常转换为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)
}
}
模块 auth
:
package services.auth
trait AuthErrors {
final case class UserNotFound(e:NotFound)extends Exception(suser $ {e.id} not found)
final case class UserAlreadyExists(email:String)extends Exception (s用户标识的$电子邮件已经存在)
...
//`toJson`只是一个扩展方法,将异常转换为JSON
def toResult e:异常):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)扩展异常(s另一个错误:$ s)
...
//`toJson`只是一个将异常转换为JSON的扩展方法
def toResult(e:Exception):Result = e match {
case AnotherError => Results.BadRequest(e.toJson)
...
case _ =>结果.InternalError(e.toJson)
}
}
看到,每个trait定义了一组异常,并提供了一个方法来将该异常转换为JSON响应,如下所示:
{
status:404,
code:not_found,
message:user 123456789123456789123456 not found,
request:https:// myhost .com / users / 123456789123456789123456
}
我想要实现的是每个模块定义其异常,重用 common
模块中定义的模块,并根据需要混合异常特征:
对象用户扩展Controller {
val errors =新的CommonErrors与AuthErrors with OtherErrors {
//这里我必须重写`toResult`编译器快乐
覆盖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)//这返回相应的结果
}
}
}
如果你看看我如何覆盖 toResult
,我总是返回 super.toResult
,它对应于trait OtherErrors
...中包含的实现,并且此实现可能会错过预期在中找到的某些模式, CommonErrors.toResult
。
肯定我错过了一些...所以问题是:什么是设计模式来解决问题 toResult
的多个实现
您可以使用可克服的特质模式。为了简化原因,我将用 .toJson
替换为 .getMessage
:
定义基本特征:
trait ErrorsStack {
def toResult(e:Exception):Result = e匹配{
case _ => Results.InternalServerError(e.getMessage)
}
}
和可堆叠特征:
trait CommonErrors扩展ErrorsStack {
case类NotFound(id:String)extends Exception(sobject $ id没有找到)
case class InvalidId(id:String)extends Exception(s$ id is a 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扩展ErrorsStack {
案例类UserNotFound(id:String)extends Exception(suser $ id not发现)
案例类UserAlreadyExists(电子邮件:String)扩展异常(s用户标识的$电子邮件已存在)
覆盖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另一个错误:$ s)
override def toResult(e:Exception):Result = e match {
case e:AnotherError => Results.BadRequest(e.getMessage)
case _ => super.toResult(e)
}
}
所以如果我们有一些堆栈
val errors =具有其他错误的AuthErrors的新CommonErrors
并定义了一些帮助器
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秒),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基因ralSecurityException(掠夺者地图))
将产生合理的输出
res0:(Int,String)=(404,用户未找到Riddle)
res1:(Int,String)=(400,由Weasley确定的用户已经存在)
res2:(Int,String)=(404,对象Gryffindor sword not found)
res3:(Int,String)=(400,另一个错误:Snape的死亡)
res4 :(Int,String)=(500,掠夺者地图)
我们也可以重构这个代码,拉案例从特征分类,并使函数更易于组合:
type Resolver = PartialFunction [Exception,Result]
object ErrorsStack {
val resolver:Resolver = {
case e => Results.InternalServerError(e.getMessage)
}
}
trait ErrorsStack {
def toResult:Resolver = ErrorsStack.resolver
}
对象CommonErrors {
case类NotFound(id:String)extends Exception(sobject $ id not found)
case class InvalidId(id:String)extends Exception(s$ id是一个无效的id)
val resolver:Resolver = {
case e:NotFound => Results.NotFound(e.getMessage)
case e:InvalidId => result.BadRequest(e.getMessage)
}
}
trait CommonErrors扩展了ErrorsStack {
override def toResult = CommonErrors.resolver orElse super.toResult
对象AuthErrors {
case类UserNotFound(id:String)extends异常(suser $ id not found)
案例类UserAlreadyExists(email:String)扩展异常(由$ email确定的用户已经存在)
val resolver:Resolver = {
case e:UserNotFound => Results.NotFound(e.getMessage)
case e:UserAlreadyExists => result.BadRequest(e.getMessage)
}
}
trait AuthErrors扩展ErrorsStack {
override def toResult = AuthErrors.resolver orElse super.toResult
对象OtherErrors {
case class AnotherError(s:String)extends Exception(s另一个错误:$ s)
val resolver:Resolver = {
case e:AnotherError => result.BadRequest(e.getMessage)
}
}
trait其他错误扩展了ErrorsStack {
override def toResult = OtherErrors.resolver orElse super.toResult
}
I have a Play application with several modules, each of which has its own exception set. Here are three examples:
Module common
:
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)
}
}
Module auth
:
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)
}
}
Module other
:
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)
}
}
As you can see, each trait defines a set of exceptions and provides a method to convert that exception to a JSON response like this:
{
"status": 404,
"code": "not_found",
"message": "user 123456789123456789123456 not found",
"request": "https://myhost.com/users/123456789123456789123456"
}
What I'm trying to achieve is to have each module defining its exceptions, reusing the ones defined in the common
module, and mixin the exception traits as needed:
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
}
}
}
If you look at how I've overridden toResult
, I always return super.toResult
, which corresponds to the implementation contained in trait OtherErrors
... and this implementation might miss some patterns that are expected to be found in CommonErrors.toResult
.
For sure I'm missing something... so the question is: what's the design pattern to fix the issue with multiple implementations of toResult
?
You could use Stackable Trait pattern. I'll replaced your .toJson
with .getMessage
for simplification reason:
define base trait:
trait ErrorsStack {
def toResult(e: Exception): Result = e match {
case _ => Results.InternalServerError(e.getMessage)
}
}
and stackable traits:
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)
}
}
so if we have some stack
val errors = new CommonErrors with AuthErrors with OtherErrors
and defined some helper
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)
}
following code
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"))
will produce reasonable output
res0: (Int, String) = (404,user Riddle not found)
res1: (Int, String) = (400,user identified by Weasley already exists)
res2: (Int, String) = (404,object Gryffindor sword not found)
res3: (Int, String) = (400,another error: Snape's death)
res4: (Int, String) = (500,Marauders's map)
also we can refactor this code, pulling case classed from traits, and make function's more composable:
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
}
这篇关于播放框架:如何实现正确的错误处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!