2015-05-20 32 views
4

我有一個Play應用程序和幾個模塊,每個模塊都有自己的異常集。這裏有三個例子:Play Framework:如何實現正確的錯誤處理

模塊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) 
    } 
} 

模塊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) 
    } 
} 

模塊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) 
    } 
} 

正如你可以看到,每個性狀定義一組例外並提供一種方法來轉換該ex ception像這樣的JSON響應:

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

我試圖做到的,是讓每個模塊定義及其例外,重用common模塊中定義的那些,和混入異常性狀需要:

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,其對應於包含在特質OtherErrors實施...這實現可能會錯過一些模式,預計在CommonErrors.toResult被發現。

肯定我錯過了一些東西......所以問題是:什麼是設計模式來解決多個實施toResult的問題?

回答

9

您可以使用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, String) = (404,object Gryffindor sword not found) 
res3: (Int, String) = (400,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 
} 
+0

很好的解釋...非常感謝! – j3d

+0

很乾淨。我很喜歡 –

相關問題