Kilka założenia:
1) Połączenie HTTP na swoim przedostatnim bloków liniowych
2) Nie mów, czy przekierowanie musi czekać na odpowiedź z wywołania HTTP, ale załóżmy, że tak.
Zablokowanie połączenia powinno zostać przeniesione do innego wątku, aby nie blokować wątków obsługujących żądania. Zagraj w dokumentach dość szczegółowo o tym. Pomocna jest funkcja Akka.future
w połączeniu z Async
.
kod Kontroler:
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}
To nieco więcej niż PHP, ale jest wielowątkowy.
Kod przekazywany do Akka.future
na linii 3 zostanie w przyszłości wywołany przy użyciu innego wątku. Jednak wywołanie Akka.future
zwraca natychmiast z numerem Future[Try]
(zobacz poniżej typ zwracanej metody biznesowej).Oznacza to, że zmienna response
ma typ . Wywołanie metody map
w linii 5 nie wywołuje kodu wewnątrz bloku mapy, raczej rejestruje ten kod (linie 6-10) jako wywołanie zwrotne. Wątek nie blokuje się w linii 5 i zwraca Future
do bloku Async
. Blok Async
zwraca graczowi AsyncResult
, który nakazuje Play zarejestrować się w celu oddzwonienia po zakończeniu przyszłości.
W międzyczasie, inny wątek wywoła numer BusinessService
z linii 3, a po powrocie wywołania HTTP do systemu zaplecza zmienna response
na linii 3 jest "ukończona", co oznacza, że wywołanie zwrotne w liniach 6-10 zostaje wywołane. result
ma typ Try
, który jest abstrakcyjny i ma tylko dwie podklasy: Success
i Failure
. Jeśli result
jest sukcesem, to metoda map
wywołuje linię 7 i zawija ją w nowym Success
. Jeśli result
jest niepowodzeniem, metoda map zwraca błąd. Metoda recover
na linii 8 działa odwrotnie. Jeśli wynikiem metody mapy jest sukces, to zwraca sukces, w przeciwnym razie wywołuje linię 9 i owija ją w Success
(a nie Failure
!). Wywołanie metody get
w linii 10 powoduje przekierowanie lub błąd z Success
i ta wartość jest używana do wypełnienia AsyncResult
, na którą gra się trzyma. Następnie gra otrzymuje wywołanie zwrotne, że odpowiedź jest gotowa i może zostać wyrenderowana i wysłana.
Za pomocą tego rozwiązania nie są zgłaszane żadne wątki, które obsługują przychodzące żądania. Jest to ważne, ponieważ na przykład na 4 rdzeniowym komputerze Play ma tylko 8 wątków zdolnych do obsługi żądań przychodzących. Nie będzie odradzać żadnych nowych, przynajmniej nie przy użyciu domyślnej konfiguracji.
Oto kod z obiektu Serwis biznesowy (całkiem dużo skopiowany kod):
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
}
Logika prezentacji i logiki biznesowej są teraz całkowicie oddzielone.
Aby uzyskać więcej informacji, zobacz https://speakerdeck.com/heathermiller/futures-and-promises-in-scala-2-dot-10 i http://docs.scala-lang.org/overviews/core/futures.html.
Jak można przetestować działanie 'deleteNode'? – EECOLOR
Dobre pytanie! Przypuszczam, że "BusinessService" nie powinien być obiektem, to może być wyśmiewany i można zrobić test na pozytywny i negatywny wynik. Więcej informacji można znaleźć na stronie http://www.playframework.com/documentation/2.1.0/ScalaTest. Czy masz na myśli konkretnie, że różne części działają w różnych wątkach? –
Ponadto, Akka.future opiera się na instancji aplikacji Play, która może zostać poddana skrótowi do testowania jednostkowego w następujący sposób: niejawna aplikacja val = Aplikacja (nowy plik ("."), This.getClass.getClassloader, Brak, Play.Mode .Dev) –