Qualche ipotesi:
1) La chiamata HTTP sul secondo blocchi ultima riga
2) Non si dice se il reindirizzamento ha bisogno di attendere la risposta dalla chiamata HTTP, ma io supponiamo che lo faccia.
La chiamata di blocco deve essere spostata su un altro thread in modo da non bloccare i thread che gestiscono le richieste. I documenti di Play sono piuttosto specifici su questo. La funzione Akka.future
combinata con Async
aiuta.
Controller cod:
1 def deleteNode(nodeId: Long) = Action { request =>
2 Async{
3 val response = Akka.future(BusinessService.businessLogic(nodeId))
4
5 response.map { result =>
6 result map {
7 Redirect(routes.NodeRender.listNodes)
8 } recover {
9 InternalServerError("Failed due to ...")
10 } get
11 }
12 }
13}
Questo è un po 'più del vostro PHP, ma è multi-threaded.
Il codice passato a Akka.future
sulla riga 3 verrà chiamato in un secondo momento in futuro utilizzando un thread diverso. Ma la chiamata a Akka.future
restituisce immediatamente con un Future[Try]
(vedere sotto per il tipo di ritorno del metodo di business).Ciò significa che la variabile response
ha il tipo Future[Try]
. La chiamata al metodo map
alla riga 5 non chiama il codice all'interno del blocco della mappa, ma registra quel codice (righe 6-10) come un callback. Il thread non si blocca sulla riga 5 e restituisce Future
al blocco Async
. Il blocco Async
restituisce un AsyncResult
da riprodurre e indica a Play di registrarsi per una richiamata al termine del futuro.
Nel frattempo, qualche altro thread farà la chiamata al BusinessService
dalla linea 3 e una volta che la chiamata HTTP che si fanno alla parte posteriore sistema restituisce fine, la variabile response
sulla linea 3 è "completato" il che significa che il viene richiamato il callback sulle linee 6-10. result
ha il tipo Try
che è astratto e ha solo due sottoclassi: Success
e Failure
. Se result
è un successo, il metodo map
chiama la linea 7 e lo avvolge in un nuovo Success
. Se result
è un errore, il metodo map restituisce l'errore. Il metodo recover
alla riga 8 fa l'opposto. Se il risultato del metodo mappa è un successo, restituisce il successo, altrimenti chiama la riga 9 e lo avvolge in un Success
(non uno Failure
!). La chiamata al metodo get
alla riga 10 rimuove il reindirizzamento o l'errore dallo Success
e tale valore viene utilizzato per completare lo AsyncResult
su cui è in attesa. Play quindi riceve una richiamata che la risposta è pronta e può essere visualizzata e inviata.
Utilizzando questa soluzione, nessun thread che gestisce le richieste in arrivo viene bloccato. Questo è importante perché, ad esempio, su una macchina a 4 core, Play ha solo 8 thread in grado di gestire le richieste in entrata. Non genererà alcun nuovo, almeno non quando si utilizza la configurazione predefinita.
ecco il codice dall'oggetto Business Service (praticamente copiato il codice):
def businessLogic(nodeId: Long): Future[Try] {
val commitDocument = Json.toJson(
Map(
"delete" -> Seq(Map("id" -> toJson(nodeId)))
))
val commitSend = Json.stringify(commitDocument)
val commitParams = Map("commit" -> "true", "wt" -> "json")
val headers = Map("Content-type" -> "application/json")
val sol = host("127.0.0.1", 8080)
val updateReq = sol/"solr-store"/"collection1"/"update"/"json" <<?
commitParams <:< headers << commitSend
val commitResponse = Http(updateReq)()
Success(commitResponse) //return the response or null, doesnt really matter so long as its wrapped in a successful Try
}
La logica di presentazione e la logica di business sono ora completamente disaccoppiati.
Vedere https://speakerdeck.com/heathermiller/futures-and-promises-in-scala-2-dot-10 e http://docs.scala-lang.org/overviews/core/futures.html per ulteriori informazioni.
Come testare l'azione 'deleteNode'? – EECOLOR
Buona domanda! Immagino che "BusinessService" non dovrebbe essere un oggetto, quindi può essere deriso e puoi fare un test per un risultato positivo e negativo. Vedi http://www.playframework.com/documentation/2.1.0/ScalaTest per maggiori dettagli. O intendi nello specifico che parti diverse vengono eseguite in thread diversi? –
Inoltre, Akka.future si basa su un'istanza dell'applicazione Play, che può essere sottoposta a test di unità in questo modo: implicit val application = Applicazione (nuovo file ("."), This.getClass.getClassloader, None, Play.Mode .Dev) –