【发布时间】:2015-06-29 22:15:23
【问题描述】:
我正在使用 Spray 应用程序中的 ask 模式调用 Actor,并将结果作为 HTTP 响应返回。我将参与者的失败映射到自定义错误代码。
val authActor = context.actorOf(Props[AuthenticationActor])
callService((authActor ? TokenAuthenticationRequest(token)).mapTo[LoggedInUser]) { user =>
complete(StatusCodes.OK, user)
}
def callService[T](f: => Future[T])(cb: T => RequestContext => Unit) = {
onComplete(f) {
case Success(value: T) => cb(value)
case Failure(ex: ServiceException) => complete(ex.statusCode, ex.errorMessage)
case e => complete(StatusCodes.InternalServerError, "Unable to complete the request. Please try again later.")
//In reality this returns a custom error object.
}
}
当 authActor 发送失败时,这可以正常工作,但如果 authActor 抛出异常,则在请求超时完成之前不会发生任何事情。例如:
override def receive: Receive = {
case _ => throw new ServiceException(ErrorCodes.AuthenticationFailed, "No valid session was found for that token")
}
我知道 Akka 文档是这么说的
要完成带有异常的未来,您需要向发件人发送失败消息。 当参与者在处理消息时抛出异常时,这不会自动完成。
但是考虑到我在 Spray 路由 Actor 和服务 Actor 之间使用了很多接口,我宁愿不使用 try/catch 包装每个子 Actor 的接收部分。有没有更好的方法来实现子actor中异常的自动处理,并在发生异常时立即解决future?
编辑:这是我目前的解决方案。但是,对每个儿童演员都这样做是相当麻烦的。
override def receive: Receive = {
case default =>
try {
default match {
case _ => throw new ServiceException("")//Actual code would go here
}
}
catch {
case se: ServiceException =>
logger.error("Service error raised:", se)
sender ! Failure(se)
case ex: Exception =>
sender ! Failure(ex)
throw ex
}
}
这样,如果它是一个预期的错误(即 ServiceException),它会通过创建一个失败来处理。如果它是意外的,它会立即返回一个失败,以便解决未来,但随后抛出异常,以便仍然可以由 SupervisorStrategy 处理。
【问题讨论】:
-
嗯...在抛出异常之前发送失败消息。
-
那是我不想做的 - 我说我宁愿不使用 try/catch 包装每个子 actor 的接收部分。这是一个玩具示例,我完全有可能无法控制抛出异常的位置。
-
嗯......你知道......弹性分布式系统的基本途径之一是“明确错误”。想想可能发生的各种错误……让它们明确。如果你能有“TypeSafe”错误……那就更好了。
-
库代码中总是存在潜在的未捕获异常。除非您知道异常是什么,或者对异常进行全面捕获,否则您无法使用失败使它们显式,这很糟糕。当然,我可以明确地因预期错误而失败,但这对我没有多大帮助。
-
嗯...编写弹性系统就是为异常做好准备。是的,总会有一些“意外”的例外。但是你知道......既然你“必须”决定系统在这些异常情况下的行为......你“需要”为它们仔细考虑和计划。这是可靠地预测系统行为的唯一方法。任何“意外”异常都是错误,因此是提高系统可靠性的机会。