0

So I tried to get a small play app communicating with another rest service. The idea is, to receive a request on the play side and then do a request to the rest api and feed parts of the result to another local actor before displaying the response from the local actor and the rest service in the browser. This image shows how

And I tried to do it with streams. I got it all working, but I am absolutely not happy with the part where I talk to my local actor and create a Future[(Future[String],Future[String]) tuple , so I would be happy if you could point me in the direction, how to do this in an elegant and clean way.

So here is my code. The input is a csv file. My local actor creates an additional graphic I want to put into the response.

def upload = Action.async(parse.multipartFormData) { request =>
 request.body.file("input").map { inputCsv =>

 //csv to list of strings
 val inputList: List[String] = convertFileToList(inputCsv)

 //http request to rest service
 val responseFuture: Future[HttpResponse] = httpRequest(inputList, "/path",4321 ,"0.0.0.0")

 //pattern match response and ask local actor
 val formattedResult = responseFuture.flatMap { response =>
  response.status match {
    case akka.http.scaladsl.model.StatusCodes.OK =>
      val resultTeams = Unmarshal(response.entity).to[CustomResultCaseClass]

     //the part I'd like to improve
      val tupleFuture = resultTeams.map(result => 
        (Future(result.teams.reduce(_ + "," + _)),
          plotter.ask(PlotData(result.eval)).mapTo[ChartPath].flatMap(plotAnswer => Future(plotAnswer.path))))
      tupleFuture.map(tuple => tuple._1.map(teams => 
        p._2.map(chartPath => Ok(views.html.upload(teams))(chartPath))))).flatMap(a => a).flatMap(b => b)
   }
 }
 formattedResult
}.getOrElse(Future(play.api.mvc.Results.BadRequest))
}
rincewind
  • 623
  • 6
  • 13

1 Answers1

0

For comprehensions are useful for this type of use cases. A basic example which demonstrates the refactoring involved:

val teamFut = Future(result.teams.reduce(_ + "," + _))

//I think the final .flatMap(Future(_.path)) is unnecessary it should be
// .map(_.path), but I wanted to replicate the question code functionality
val pathFut = plotter.ask(PlotData(result.eval))
                     .mapTo[ChartPath]
                     .flatMap(Future(_.path))

val okFut = 
  for {
    teams     <- teamFut
    chartPath <- pathFut      
  } yield Ok(views.html.upload(teams))(chartPath)

Note: the Initial Futures should be instantiated outside of the for otherwise parallel execution won't occur.

Community
  • 1
  • 1
Ramón J Romero y Vigil
  • 17,373
  • 7
  • 77
  • 125
  • Thank you! This seems to be exactly the right tool here. Do you in general think the way I implemented it is the a good way or would you approach it in another way? – rincewind Feb 26 '16 at 21:14
  • @rincewind You are welcome. In general your implementation seems fine. Just be careful of overusing Futures, there is a significant initial cost at instantiation. For example, `result.teams` would have to be pretty big to warrant a Future for just a `reduce`. Happy hacking. – Ramón J Romero y Vigil Feb 26 '16 at 22:29