Несколько WS-вызовов в одном действии, как обрабатывать объекты Promise?

Я создаю небольшой сервер в PlayFramework2/ Scala, который должен извлекать данные из нескольких WS (REST/JSON), манипулировать данными из тезисов WS, а затем составлять и возвращать результат.

Я знаю, как вызвать один WS, манипулировать данными и возвращать ответ Async. Но я не знаю, как называть последовательно несколько веб-сервисов, обрабатывать данные между каждым вызовом и генерировать агрегированный ответ.

Пример:

  • Получить список моих предпочтительных композиций из WebService A
  • затем для каждой песни выберите детали исполнителя из WS B (один вызов по песне)
  • затем, сгенерируйте и верните что-то (например, агрегированный список), используя ответы A и B
  • тогда верните результат

Я заблокирован асинхронными процессами WS API (WS.url(url).get => Promise[Response]). Должен ли я наклоняться к Акке, чтобы решить эту проблему?

Спасибо.

Ответы

Ответ 1

flatMap и map - ваши друзья! Эти два метода типа Promise позволяют преобразовать результат Promise[A] в другой Promise[B].

Вот простой пример из них в действии (я намеренно написал явно больше аннотаций типа, чем нужно, чтобы помочь понять, где происходят преобразования):

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

Без ненужных аннотаций типа и использования обозначения "для понимания" вы можете написать следующий более выразительный код:

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