2012-04-03 10 views
11

Sviluppo un piccolo server in PlayFramework2/Scala che deve recuperare i dati da più WS (REST/JSON), manipolare i dati da questi WS, quindi comporre e restituire un risultato.Chiamata WS multipla in un'unica azione, come gestire gli oggetti Promise?

So come chiamare uno WS, manipolare i dati e restituire una risposta asincrona. Ma non so come chiamare successivamente diversi servizi web, gestire i dati tra ogni chiamata e generare una risposta aggregata.

Esempio:

  • recuperare l'elenco delle mie canzoni prefered da WebService Un
  • poi, per ogni canzone, recuperare i dettagli artista WS B (una chiamata dalla canzone)
  • quindi, generare e restituire qualcosa (elenco aggregato per esempio) utilizzando A e B ses
  • poi, restituisce il risultato

Sono bloccata elaborazioni asincroni di WS API (WS.url(url).get => Promise[Response]). Devo appoggiarmi ad Akka per risolvere questo problema?

Grazie.

risposta

19

flatMap e map sono i tuoi amici! Questi due metodi del tipo Promise consentono di trasformare il risultato di un Promise[A] in un altro Promise[B].

Ecco un semplice esempio di loro in azione (Ho volutamente scritto in modo esplicito più annotazioni di tipo quanto necessario, solo per aiutare a capire dove avvengono trasformazioni):

def preferredSongsAndArtist = Action { 
    // Fetch the list of your preferred songs from Web Service “A” 
    val songsP: Promise[Response] = WS.url(WS_A).get 
    val resultP: Promise[List[Something]] = songsP.flatMap { respA => 
    val songs: List[Song] = Json.fromJson(respA.json) 
    // Then, for each song, fetch the artist detail from Web Service “B” 
    val result: List[Promise[Something]] = songs.map { song => 
     val artistP = WS.url(WS_B(song)).get 
     artistP.map { respB => 
     val artist: Artist = Json.fromJson(respB.json) 
     // Then, generate and return something using the song and artist 
     val something: Something = generate(song, artist) 
     something 
     } 
    } 
    Promise.sequence(result) // Transform the List[Promise[Something]] into a Promise[List[Something]] 
    } 
    // Then return the result 
    Async { 
    resultP.map { things: List[Something] => 
     Ok(Json.toJson(things)) 
    } 
    } 
} 

Senza le annotazioni di tipo non necessarie e utilizzando la “ per la comprensione "notazione, è possibile scrivere il seguente codice più espressivo:

def preferredSongsAndArtist = Action { 
    Async { 
    for { 
     // Fetch the list of your preferred songs from Web Service “A” 
     respA <- WS.url(WS_A).get 
     songs = Json.fromJson[List[Song]](respA.json) 
     // Then, for each song, fetch the artist detail from Web Service “B” 
     result <- Promise.sequence(songs.map { song => 
     for { 
      respB <- WS.url(WS_B(song)).get 
      artist = Json.fromJson[Artist](respB.json) 
     } yield { 
      // Then, generate and return something using the song and artist 
      generate(song, artist) 
     } 
     }) 
    // Then return the result 
    } yield { 
     Ok(Json.toJson(result)) 
    } 
    } 
} 
+0

Grazie per la risposta. Analizzo e collaudo questa soluzione al più presto. – YoT

+0

@julien Cosa succede se uno dei servizi Web chiama timeout o restituisce un 500? getOrElse? –

+1

La promessa sarà riscattata con un valore [Gettato] (http://www.playframework.org/documentation/api/2.0/scala/play/api/libs/concurrent/Thrown.html). Puoi gestire questo caso usando [extend] (http://www.playframework.org/documentation/api/2.0/scala/index.html#play.api.libs.concurrent.Promise) per esempio. –