Gestion des erreurs lors de la combinaison de plusieurs appels WS en un seul résultat

StackOverflow https://stackoverflow.com//questions/12666169

  •  11-12-2019
  •  | 
  •  

Question

J'apprends Scala, Play et les services Web en même temps, alors soyez indulgents avec moi.J'ai mis en place un petit service d'agrégation qui combine un service Web météo et les services Web de géocodage et de localisation de Google.J'ai quelque chose qui fonctionne mais je suis un peu confus quant à la bonne façon de gérer les erreurs.(J'ai posté le code à la fin du post)

Ainsi, l'API Places utilise la latitude/longitude et j'utilise donc l'API de géocodage pour obtenir la latitude/longitude à partir du code postal.Lors du traitement de la réponse de l'appel à l'API de géocodage, je me retrouve avec un (Option[String], Option[String]) (tenu dans le maybeLoc val).À l'intérieur de la déclaration de correspondance qui vérifie maybeLoc, si cela finit par être (None, None), Je retourne Promise() parce que je dois retourner un Promise à partir de l'appel flatmap.

J'ai deux questions à ce sujet:

1.) Quelle est la bonne façon de gérer le cas où vous ne pouvez effectuer aucun traitement supplémentaire à l'intérieur de l'un de ces appels flatMap ou map ?Cela m'oblige à rendre une promesse, mais en faisant un message vide Promise cela expirera juste lorsque je vais racheter, cela semble être une très mauvaise idée.

2.) Ai-je raison de supposer que l'appel à Promise() crée un objet de promesse vide qui expirera toujours lorsque vous tenterez de le racheter ?Je ne pouvais pas vraiment le dire grâce au scaladoc et je n'ai rien trouvé à ce sujet sur Google.

J'espère que mes questions vous semblent compréhensibles et suffisamment claires.Voici le code :

def bothAsJson(zipcode:String) = Action {
    val promiseOfLoc = Geocode.buildUrlFor(zipcode).get()
    val promiseOfWeather = Weather.buildUrlFor(zipcode, "json").get()

    val result = promiseOfLoc.flatMap { locResp => 
        val maybeLoc = Geocode.extractLocation(locResp.body.toString())
        maybeLoc match {
            case (Some(lat), Some(lng)) => {
                val promiseOfPlaces = Places.buildUrlFor(lat,lng).get()
                promiseOfPlaces.flatMap { placesResp =>
                    promiseOfWeather.map { weatherResp =>
                        (weatherResp.body.toString(), placesResp.body.toString())
                    }
                }
            }
            case _ => Promise()
        }
    }

    Async {
        result.orTimeout("Timeout!", 2000).map {response =>
            response.fold(
                result => Ok("Got:\n\nweather:\n" + result._1 + "\n\nplaces:\n" + result._2),
                timeout => InternalServerError(timeout)
            )
        }
    }
}
Était-ce utile?

La solution

Si vous obtenez (Aucun, Aucun), vous ne devriez pas chercher à expirer, mais renvoyer un autre message d'erreur, je crois.J'ai fourni un exemple ci-dessous.

Je pense que vous avez besoin d'OptionT de Scalaz 7.J'écrirais ceci ainsi :

import scalaz._
import Scalaz._

def bothAsJson(zipcode:String) = Action {
    val promiseOfLoc = Geocode.buildUrlFor(zipcode).get.map { Option(_.body.toString()) }
    val promiseOfWeather = Weather.buildUrlFor(zipcode, "json").get
       .map{ lockResp => 
           val (lat,lng) = Geocode.extractLocation(locResp.body.toString())
           (lat |@| lng).tupled
       }
    def buildPlaces(lat: String, lng: String) = Places.buildUrlFor(lat,lng).get
       .map { Option(_.body.toString) }

    val result = (for {
       (lat, lng) <- OptionT(promiseOfLoc)
       places     <- OptionT(Places.buildUrlFor(lat,lng).get())
       weather    <- OptionT(promiseOfWeather)
    } yield (places, weather)).run

    Async {
        result.orTimeout("Timeout!", 2000).map {response =>
            response.fold(
                result => {
                  result.map(
                   some => Ok("Got:\n\nweather:\n" + some._1 + "\n\nplaces:\n" + some._2)
                  ).getOrElse(BadRequest("lat/lng failed probably?"))
                },
                timeout => InternalServerError(timeout)
            )
        }
    }
}

Avec OptionT nous pouvons flatMap comme s'il s'agissait d'un Option, acquérant la possibilité de ne rien traiter si nous obtenons un None.A la fin, nous nous retrouvons avec un Promise[Option[T]] ce qui est très sympa pour ça.Un autre bon moyen de gérer les erreurs consiste à utiliser l'un ou l'autre/EtherT avec la même méthode.

|@| est un constructeur applicatif.Il faut 2 options, et renvoyer un Option((Int, Int)) si les deux côtés sont Some.Si un côté ou les deux côtés sont None, il renvoie un None.

Notez que pour que cela fonctionne, vous aurez besoin d'un scalaz Monad[Promise] exemple

implicit val PromiseInstance = new Monad[Promise] {
  // override def map[A,B](fa: Promise[A])(f: A => B) = fa.map(f)
  def point[A](a: => A) = Promise.pure(a)
  def bind[A,B](fa: Promise[A])(f: A => Promise[B]) = fa.flatMap(f)
}

Veuillez également noter que j'ai écrit tout ce code dans l'éditeur SO, il se peut qu'il manque des accolades.Mais tout le code devrait être plus ou moins correct, j'en ai testé des parties dans la réplique.

N'hésitez pas à demander de l'aide sur #scalaz sur freenode irc, ou sur les groupes google scalaz.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top