2012-04-03 2 views
11

Ich entwickle einen kleinen Server in PlayFramework2/Scala, der Daten von mehreren WS (REST/JSON) abrufen, die Daten von diesen WS manipulieren, dann komponieren und ein Ergebnis zurückgeben muss.Mehrere WS-Aufruf in einer Aktion, wie mit Promise-Objekten umzugehen?

Ich weiß, wie eine WS aufrufen, manipulieren Sie die Daten und eine asynchrone Antwort zurück. Aber ich weiß nicht, wie man sukzessive mehrere Web-Dienste aufrufen, die Daten zwischen jedem Anruf behandeln und eine aggregierte Antwort generieren.

Exemple:

  • Fetch die Liste meiner bevorzugten Songs aus WebService A
  • dann, für jeden Song, holen Sie den Künstler Detail von WS B (ein Anruf von Lied)
  • dann, generieren und etwas (aggregierte Liste zum Beispiel) mit der A und B respon
  • ses
  • Dann gibt das Ergebnis

I durch die asynchronen Verarbeitungen der WS API blockiert bin (WS.url(url).get => Promise[Response]). Muss ich mich auf Akka stützen, um dieses Problem zu lösen?

Vielen Dank.

Antwort

19

flatMap und map sind Ihre Freunde! Diese zwei Verfahren des Typs Promise ermöglichen es, das Ergebnis eines Promise[A] in ein anderes Promise[B] zu transformieren.

Hier ist ein einfaches Beispiel, sie in Aktion ist (ich schrieb absichtlich explizit mehr Typenannotationen als nötig, nur um zu helfen, zu verstehen, wo Transformationen geschehen):

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

Ohne die unnötigen Typenannotationen und mit dem „ Für das Verständnis "Notation, können Sie folgenden aussagekräftigeren Code schreiben:

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

Vielen Dank für Ihre Antwort. Ich analysiere und teste diese Lösung so schnell wie möglich. – YoT

+0

@julien Was passiert, wenn einer der Web-Service-Aufrufe Timeout oder 500 zurückgeben? getOrElse? –

+1

Das Versprechen wird mit einem [Thrown] (http://www.playframework.org/documentation/api/2.0/scala/play/api/libs/concurrent/Thrown.html) Wert eingelöst. Sie können diesen Fall behandeln, indem Sie beispielsweise [extend] (http://www.playframework.org/documentation/api/2.0/scala/index.html#play.api.libs.concurrent.Promise) verwenden. –

Verwandte Themen