2012-04-03 11 views
11

Desarrollo un pequeño servidor en PlayFramework2/Scala que tiene que recuperar datos de múltiples WS (REST/JSON), manipular los datos de estos WS, luego componer y devolver un resultado.Múltiples llamadas de WS en una acción, ¿cómo manejar los objetos Promesa?

Sé cómo llamar al uno WS, manipular los datos y devolver una respuesta Async. Pero no sé cómo llamar al sucesivamente varios servicios web, manejar los datos entre cada llamada y generar una respuesta agregada.

Ejemplo:

  • buscar a la lista de mis canciones preferidas de WebService Un
  • entonces, para cada canción, ir a buscar el detalle artista WS B (una llamada por canción)
  • luego, generar y devolver algo (lista agregada, por ejemplo) utilizando el A y B respon ses
  • a continuación, devuelve el resultado

estoy bloqueado por los procesamientos asíncronos de WS API (WS.url(url).get => Promise[Response]). ¿Tengo que apoyarme en Akka para resolver este problema?

Gracias.

Respuesta

19

flatMap y map son tus amigos! Estos dos métodos del tipo Promise permiten transformar el resultado de Promise[A] en otro Promise[B].

Aquí es un ejemplo sencillo de ellos en acción (escribí intencionadamente de forma explícita más anotaciones de tipo de lo necesario, sólo para ayudar a entender dónde ocurren transformaciones):

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)) 
    } 
    } 
} 

Sin las anotaciones de tipo innecesarias y utilizando el “ para la notación de comprensión, puede escribir el siguiente código más expresivo:

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

Gracias por su respuesta. Analizo y pruebo esta solución lo antes posible. – YoT

+0

@julien ¿Qué sucede si cualquiera de las llamadas al servicio web expira o devuelve un 500? getOrElse? –

+1

La promesa se canjeará con un valor [Lanzado] (http://www.playframework.org/documentation/api/2.0/scala/play/api/libs/concurrent/Thrown.html). Puede manejar este caso usando [extender] (http://www.playframework.org/documentation/api/2.0/scala/index.html#play.api.libs.concurrent.Promise) por ejemplo. –

Cuestiones relacionadas