2016-11-14 52 views
2

我真的一直在Scala和Play的這些概念中掙扎。我想更新我的數據庫,我想我需要將我的數據庫更新函數包裝在Future中,但我不知道如何返回所需的play.api.mvc.Result瞭解Scala Play行爲和期貨

我在斯卡拉控制器返回響應一些:

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?我是否想將它包裝在未來中並檢查它是否成功完成?如果我檢查,我是否在Success方法上返回正確的Result

現在,我不理解如何構造這些東西或如何實際實施基於Future的解決方案。

+0

如果你的數據庫函數不返回以後,就沒有意義了未來包裝它除了滿足類型檢查。 – rethab

+0

@rethab對不起,但不包裝在未來將它扔進異步池?否則,請求會阻止嗎? – NeedingHelp

+0

@rethab另外,我想返回操作的成功或失敗。我應該只使用拋出的SomeException?這是慣用的和最好的拱? – NeedingHelp

回答

1

你的updateMyDatabase函數應該返回一些沒有單位值,以告訴它是否成功。有多個響應數據庫操作可以返回:

  1. 數據庫錯誤,拋出異常
  2. 未找到行,沒有更新發生
  3. 行發現,並更新

所以一個Try [布爾]將是一個很好的類型來處理所有這些情況。

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

我們現在可以對響應進行匹配,並返回正確的Result類型。

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

由於getSomethingFuture和getSomethingElseFutures都回來期貨,你已經是一個未來的環境中工作,並且不需要任何包裝的結果在未來。 yield關鍵字將確保將yield單元中的任何內容重新包裝回Future中。

現在您仍然需要處理getSomethingFuture或getSomethingElseFuture失敗的情況。爲此,您可以使用恢復功能。因此,您的最終代碼會是這個樣子:

(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) 
+0

導入遊戲的默認執行上下文基本上是不鼓勵的,因爲它取決於底層的全局狀態。你應該注入它。 – rethab

+0

如何注入默認的執行上下文?該文檔僅顯示導入它的示例,並僅將注入用於自定義執行上下文。 – soote

+0

只需聲明一個'scala.concurrent.ExecutionContext'。 Play在'play.api.inject.BuiltinModule'中定義了綁定。 – rethab

0

@ soote的答覆工作,但一個原因,updateMyDatabase返回一個未來是這樣你就可以統一的錯誤使用Future#recover處理。另一個原因是,所以你可以重用在其他地方的方法,因爲玩那種期待阻塞操作中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) 
     } 
    } 
}