了解Scala播放操作和期货

时间:2016-11-14 05:49:09

标签: scala playframework playframework-2.0 future

我在Scala和Play中一直在努力解决其中的一些概念。我想更新我的数据库,我想我需要将我的数据库更新功能包装在Future中,但我不知道如何返回所需的play.api.mvc.Result

我在Scala中有一个控制器,它返回一些响应:

def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
    request.body.validate[MyObject].map { myObject =>
        for {
            getSomething <- getSomethingFuture
            getSomethingElse <- getSomethingElseFuture
        } yield {
            if (getSomethingElse) {
                if (getSomething)
                    updateMyDatabase(myObject)//perform database request
                else
                    BadRequest("Invalid request")
            }
        } else {
            // do some other things
        }
    }
}

private [this] def updateMyDatabase(myObject: MyObject) {
    // do things to update the database
}

updateMyDatabase应该返回Result吗?我想将它包装在Future中并检查它是否成功完成?如果我检查,那么我会在Result方法上返回正确的Success吗?

目前,我不了解如何构建这些内容或如何实际实现基于Future的解决方案。

2 个答案:

答案 0 :(得分:1)

您的updateMyDatabase函数应该返回一些无单位值,以告知它是否成功。数据库操作可以返回多个响应:

  1. 数据库错误,抛出异常
  2. 未找到行,未发生更新
  3. 找到
  4. 行,并更新了
  5. 因此,Try [Boolean]将是处理所有这些场景的好类型。

    private [this] def updateMyDatabase(myObject: MyObject): Try[Boolean] = {
        // do things to update the database
    }
    

    我们现在可以匹配响应,并返回正确的结果类型。

    updateMyDatabase(myObject) match {
        case Failure(exception) => BadRequest
        case Success(b) => if (b) Ok else BadRequest
    }
    

    由于getSomethingFuture和getSomethingElseFutures都返回Futures,因此您已经在Future的上下文中工作,并且不需要在Future中包装任何结果。 yield关键字将确保将yield体中的任何内容重新转换回Future。

    现在你仍然需要处理getSomethingFuture或getSomethingElseFuture失败的情况。为此,您可以使用recover功能。所以你的最终代码看起来像这样:

    (for {
        getSomething <- getSomethingFuture
        getSomethingElse <- getSomethingElseFuture
    } yield {
        // this code only executes if both futures are successful.
        updateMyDatabase(myObject) match {
            case Failure(exception) => BadRequest
            case Success(b) => if (b) Ok else BadRequest
        }
    }) recover {
        // Here you can match on different exception types and handle them accordingly.
        // So throw a specific exception for each task if you need to handle their failures differently.
        case e: GetSomethingFutureFailed => BadRequest
        case e: GetSomethingElseFutureFailed => BadRequest
        case _ => BadRequest
    }
    

    来自游戏documentation:请注意,您可能想要将您的阻止代码包装在期货中。这不会使它成为非阻塞,它只是意味着阻塞将在不同的线程中发生。您仍然需要确保您使用的线程池有足够的线程来处理阻塞。

    还要确保指示控制器注入执行上下文,如下所示:

    import scala.concurrent.ExecutionContext
    class AsyncController @Inject() (...)(implicit exec: ExecutionContext)
    

答案 1 :(得分:0)

@ soote的答案有效,但让updateMyDatabase返回未来的一个原因是,您可以使用Future#recover统一错误处理。另一个原因是你可以在其他地方重用该方法,因为Play类需要在Future中完成阻塞操作

如果updateMyDatabase返回Future[_],您可以执行以下操作:

def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
    request.body.validate[MyObject].map { myObject =>
        val futResult = for {
            getSomething <- getSomethingFuture
            getSomethingElse <- getSomethingElseFuture
            if getSomethingElse
            if getSomething
            _ <- updateMyDatabase(myObject)
        } yield Ok("")

        futResult.recover {
            case e: Exception1 => BadRequest("bad request")
            case e: Exception2 => BadRequest("blah")
            case e => InternalServerError(e.getMessage)
        }
    }
}

private [this] def updateMyDatabase(myObject: MyObject): Future[Unit] = ???

您可能想知道如何更精细地处理错误。这是一种方式:

def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
    request.body.validate[MyObject].map { myObject =>
        val futResult = for {
            getSomething <- getSomethingFuture
            getSomethingElse <- getSomethingElseFuture
            _ <- if(getSomethingElse && getSomething) {
                   updateMyDatabase(myObject)
                 } else {
                   Future.failed(new CustomException("Couldn't get something else"))
                 }
        } yield Ok("")

        futResult.recover {
            case e: CustomException => BadRequest("failed to get something else")
            case e: Exception2 => BadRequest("blah")
            case e => InternalServerError(e.getMessage)
        }
    }
}

这是另一种方式:

def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
    request.body.validate[MyObject].map { myObject =>
        val futResult = for {
            getSomething <- getSomethingFuture
            getSomethingElse <- getSomethingElseFuture
            result <- if(getSomethingElse && getSomething) {
                        updateMyDatabase(myObject).map(_ => Ok(""))
                      } else {
                        Future.successful(BadRequest("failed to get something else"))
                      }
        } yield result

        futResult.recover {
            case e: Exception2 => BadRequest("blah")
            case e => InternalServerError(e.getMessage)
        }
    }
}