在Controller中使用Future的最佳实践(Play + Scala)

时间:2018-01-23 01:06:26

标签: scala playframework future

在我的Play网络应用中,我使用val resultRack = Await.result(futureList, Duration.Inf)来获取Future的结果。还有另一种更好的方法(使用最佳实践)从数据库中获取结果吗?如果我使用onCompleteonSuccess我的COntroller完成执行,结果不在val。下面是我的Controller方法。一切正常,但我需要在Scala中遵循更多最佳实践。

已修改:我已在其他方法上使用Action.async。但在这个我基本上不能使用either.fold。我想我需要map围绕方法的所有代码才能验证json。

  def addRack = Action(parse.json) { request =>
    val either = request.body.validate[Rack]
    either.fold(
      errors => BadRequest("invalid json Rack.\n"),
      rack => {
        val f: Future[Option[RackRow]] = rackRepository.getById(rack.id)
        val result = Await.result(f, Duration.Inf)
        result match {
          case Some(r) =>
            // If the Rack already exists we update the produced and currentTime properties
            val fGpu: Future[Seq[GpuRow]] = gpuRepository.getByRack(r.id)
            // val total = fGpu.map(_.map(_.produced).sum)
            val resultGpu = Await.result(fGpu, Duration.Inf)
            val total = resultGpu.map(_.produced).sum
            rackRepository.update(r.id, Some(total), Some(System.currentTimeMillis))
            Ok("Rack already exists! Updated produced and currentTime.\n")
          case None =>
            // If the Rack does not exist we create it.
            val rackRow = RackRow(rack.id, rack.produced, System.currentTimeMillis)
            rackRepository.insert(rackRow)
            Ok
        }
      }
    )
  }
使用flatMap和map

新方法。我的问题是我在Controller中创建并填充seq rackSeq。我不会评估用于创建此对象的gpuSeq,因为它来自Future。我该怎么做才能评估这个未来gpeSeq?在我的搜索结果中,我只能看到rackSeq,但gpuSeq的列表始终为空。

此外,如果代码Util.toTime(at)引发错误,我无法用recover来识别它。正如我通过答案所理解的那样,我可以做到这一点....

  def getRacks(at: String) = Action.async { implicit request: Request[AnyContent] =>

    var rackSeq: Seq[Rack] = Seq.empty
    var gpuSeq: Seq[Gpu] = Seq.empty

    rackRepository.get(Util.toTime(at)).flatMap { resultRack: Seq[RackRow] =>
      resultRack.map { r: RackRow =>
        gpuRepository.getByRack(r.id).map { result: Seq[GpuRow] =>
          result.map { gpuRow: GpuRow =>
            gpuSeq = gpuSeq :+ Gpu(gpuRow.id, gpuRow.rackId, gpuRow.produced, Util.toDate(gpuRow.installedAt))
            println(gpuRow)
          }
        }
        val rack = Rack(r.id, r.produced, Util.toDate(r.currentHour), gpuSeq)
        rackSeq = rackSeq :+ rack
      }

      //      val result = Await.result(listGpu, Duration.Inf)
      //      result.foreach { gpuRow =>
      //        gpuSeq = gpuSeq :+ Gpu(gpuRow.id, gpuRow.rackId, gpuRow.produced, Util.toDate(gpuRow.installedAt))
      //      }
      Future.successful(Ok(Json.toJson(rackSeq)).as(JSON))
    }.recover {
      case pe: ParseException => BadRequest(Json.toJson("Error on parse String to time."))
      case e: Exception => BadRequest(Json.toJson("Error to get racks."))
      case _ => BadRequest(Json.toJson("Unknow error to get racks."))
    }
  }

3 个答案:

答案 0 :(得分:8)

不要在Play控制器中使用Await.result。这将阻止线程并杀死使用像Play这样的反应式框架的主要好处之一。而是mapflatMap Future生成Result。例如,假设您有以下RackRepository

class RackRepository {
  def racks: Future[Seq[Rack]] = ???
}

在你的控制器中,而不是:

def wrong = Action {
  val racks: Future[Seq[Rack]] = rackRepository.racks
  // This is wrong, don't do that
  val racksSeq = Await.result(racks, Duration.Inf)
  Ok(Json.toJson(racksSeq))
}

您所做的就是使用Action.async并映射您的未来以生成结果:

def list = Action.async {
  rackRepository.racks.map { racks =>
    Ok(Json.toJson(racks))
  }
}

如果您需要嵌套多个未来结果,请改用flatMap

编辑:

从第一个示例开始,您需要了解mapflatMap之间的区别。这个看起来是一个好的开始:

Futures - map vs flatmap

让我们看看一些例子:

val firstFuture: Future[String] = ??? // it does not mater where it comes from
val secondFuture: Future[String] = ??? // it does not mater where it comes from

val f1: Future[Int] = firstFuture.map(_.toInt)
val f2: Future[Future[String]] = firstFuture.map(secondFuture)
val f3: Future[String] = firstFuture.flatMap(secondFuture)

// Let's start to combine the future values
val f4: Future[Future[String]] = firstFuture.map { first =>
  secondFuture.map { second =>
    first + second // concatenate
  }
}

// But what if we want a Future[String] instead of a Future[Future[String]]?
// flatMap to the rescue!
val f5: Future[String] = firstFuture.flatMap { first =>
  secondFuture.map { second =>
    first + second // concatenate
  }
}

请参阅?没有Await。然后我们有你的代码:

val fGpu: Future[Seq[GpuRow]] = gpuRepository.getByRack(r.id)
// val total = fGpu.map(_.map(_.produced).sum)
val resultGpu = Await.result(fGpu, Duration.Inf)

为什么不像flatMap那样合并mapf5?换句话说,为什么要Await fGpu而不是map才能返回Future[Result]

gpuRepository.getByRack(r.id).map { gpuRows =>
  val total = gpuRows.map(_.produced).sum
  rackRepository.update(r.id, Some(total), Some(System.currentTimeMillis))
  Ok("Rack already exists! Updated produced and currentTime.\n")
}

当然,您需要Action.async使用flatMapf

答案 1 :(得分:5)

关于你的代码,然后关于你未来的问题,这里有很多事情:

  1. 不要将控制器与模型混合:一般来说,控制器是一组方法(在控制器类中),它获取请求并返回结果(OKREDIRECT等)。模型是类/对象/接口中的一组方法,它们获取一组参数,处理外部资源并将其结果返回给控制器。

  2. 方法是您的朋友:您可以将代码更多地划分为不同的方法。例如,您的方法名称为addRack,但其正文也包含一些处理,您可以将它们放在控制器或模型中的不同方法中;取决于他们属于何处。

  3. 永不等待:有一个原因,就是你占用了线程,并且在等待时间内不会让它们独自存在。这将导致您的应用在内存和CPU使用方面效率低下。

  4. 地图是您的朋友:在调用返回未来的方法时使用地图。例如,您想要调用此方法:

  5. def hiFromFuture : Future[String] = Future{...}

    hiFromFuture.map{
      futureResult: String => //when Future is successful 
    }
    

    如果您有多个连续的未来电话,也应该使用flatmap。例如,假设hiFromFuture2hiFromFuture具有相同的签名/正文:

    hiFromFuture.map{
      futureResult: String => hiFromFuture2.map{ futureResult => }
    
    }
    

    应写成:

    hiFromFuture.flatMap{
      futureResult: String => //when first future is successful 
        hiFromFuture2.map{ 
          futureResult => //when second future is successful 
        }
    }
    

    避免Future[Future[String]];得到Future[String]

    1. 恢复失败的未来:如果你没有得到结果怎么办?你使用恢复。例如:

      hiFromFuture.map{gotData => Ok("success")}.recover{case e: Exception => BadRequest}

    2. 请注意,您可以使用恢复块中预期的任何异常。

答案 2 :(得分:2)

如果您的问题是如何管理json验证的错误情况,那么在成功路径将返回Future的上下文中,您只需将Result包裹在已成功完成的Future中{{} 1}},即Future.successful(BadRequest(...))。如下所示

def addRack = Action(parse.json).async { request =>
  val either = request.body.validate[Rack]
  either.fold(
    errors => Future.successful(BadRequest("invalid json Rack.\n")),
    rack => {
      rackRepository.getById(rack.id).map {
        case Some(r) =>
          //...

          Ok("Rack already exists! Updated produced and currentTime.\n")
        case None =>
          //...

          Ok
      }
    }
  )
}

然后当你有嵌套的期货时,你应该按照marcospereira和Dave Rose的说明进行flatmap