I develop a little server in PlayFramework2/Scala which has to retrieve data from multiple WS (REST/JSON), manipulate the data from theses WS, then compose and return a result.
I know how to call one WS, manipulate the data and return an Async response. But I don't know how how to call successively several web-services, handle the data between every call and generate an aggregated answer.
Exemple :
- Fetch the list of my prefered songs from WebService A
- then, for each song, fetch the artist detail from WS B (one call by song)
- then, generate and return something (aggregated list for example) using the A and B responses
- then, return the result
I am blocked by the asynchronous processings of WS API (WS.url(url).get => Promise[Response]
). Do I have to lean on Akka to solve this problem?
Thank you.
flatMap
and map
are your friends! These two methods of the Promise
type allow to transform the result of a Promise[A]
into another Promise[B]
.
Here is a simple example of them in action (I intentionally wrote explicitly more type annotations than needed, just to help to understand where transformations happen):
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))
}
}
}
Without the unnecessary type annotations and using the “for comprehension” notation, you can write the following more expressive code:
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))
}
}
}