2012-02-10 15 views
22

W ciągu ostatnich kilku miesięcy wraz z moimi współpracownikami zbudowaliśmy system po stronie serwera służący do wysyłania powiadomień push do urządzeń iPhone. Zasadniczo użytkownik rejestruje się dla tych powiadomień za pośrednictwem usługi RESTful webservice (Spray-Server, ostatnio zaktualizowanej do używania Spray-can jako warstwy HTTP), a logika planuje jedną lub wiele wiadomości do wysyłki w przyszłości, korzystając z programu planującego Akka.Usługa internetowa na bazie aktorów - jak to zrobić właściwie?

System ten, jak go zbudowaliśmy, działa po prostu: może obsłużyć setki, a może nawet tysiące żądań HTTP na sekundę, i może wysyłać powiadomienia z szybkością 23 000 na sekundę - być może nawet więcej, jeśli zmniejszymy wyjście dziennika , dodaj wielu aktorów nadawcy powiadomień (a tym samym więcej połączeń z Apple), i może być pewna optymalizacja, którą należy wykonać w bibliotece Javy, której używamy (java-apns).

To pytanie dotyczy tego, jak to zrobić Dobrze (tm). Mój kolega, o wiele więcej wiedzy na temat systemów Scala i systemów opartych na aktorach, zauważył, że aplikacja nie jest "czystym" systemem opartym na aktorach - i ma rację. Zastanawiam się teraz, jak to zrobić Dobrze.

W tej chwili mamy pojedynczego aktora Spray HttpService, który nie jest podklasowany, który jest inicjowany zestawem dyrektyw, które przedstawia naszą logikę usług HTTP. Obecnie bardzo uproszczone, mamy dyrektyw tak:

post { 
    content(as[SomeBusinessObject]) { businessObject => request => 
    // store the business object in a MongoDB back-end and wait for the ID to be 
    // returned; we want to send this back to the user. 
    val businessObjectId = persister !! new PersistSchedule(businessObject) 
    request.complete("/businessObject/%s".format(businessObjectId)) 
    } 
} 

Teraz, jeśli mogę to prawo „czekając na odpowiedź” z aktorem jest no-no w programowaniu aktor oparte (plus! ! jest przestarzałe). Uważam, że "właściwym" sposobem jest przekazanie obiektu request do aktora persister w wiadomości i wywołanie go pod numerem request.complete, gdy tylko otrzyma on wygenerowany identyfikator z zaplecza.

Przepisałem jedną z tras w mojej aplikacji, aby to zrobić; w wiadomości wysyłanej do aktora wysyłany jest także obiekt/referencja żądania. To wydaje się działać tak jak to miało:

content(as[SomeBusinessObject]) { businessObject => request => 
    persister ! new PersistSchedule(request, businessObject) 
    } 

Moim głównym problemem jest to, że wydaje się przekazać obiekt request do „logiki biznesowej”, w tym przypadku persister. Aktywator otrzymuje teraz dodatkową odpowiedzialność, tj. Wywołanie request.complete i wiedzę na temat systemu, w którym działa, tj. Że jest częścią usługi internetowej.

Jaki byłby właściwy sposób postępowania z taką sytuacją, aby nieodłączny aktor nie wiedział, że jest częścią usługi http, i nie musi wiedzieć, jak wygenerować wygenerowany identyfikator?

myślę, że wniosek powinien być nadal przekazywane do aktora persister, ale zamiast request.complete persister aktor dzwoni, wysyła komunikat do aktora HttpService (a wiadomości SchedulePersisted(request, businessObjectId)), który po prostu wywołuje request.complete("/businessObject/%s".format(businessObjectId)) . Zasadniczo:

def receive = { 
    case SchedulePersisted(request, businessObjectId) => 
    request.complete("/businessObject/%s".format(businessObjectId)) 
} 

val directives = post { 
    content(as[SomeBusinessObject]) { businessObject => request => 
    persister ! new PersistSchedule(request, businessObject) 
    } 
} 

Czy jestem na dobrej drodze z takim podejściem?

Mniejsze drugorzędne konkretne pytanie, czy można podklasować HttpService i nadpisać metodę odbioru, czy też będę łamał rzeczy w ten sposób?(Nie mam pojęcia o aktorach podklasujących, ani jak przekazywać nierozpoznane komunikaty do aktora "rodzica")

Ostatnie pytanie, przekazuje obiekt/odniesienie w wiadomościach dla aktorów, które mogą przejść przez całą aplikację w porządku. lub czy istnieje lepszy sposób "zapamiętania", jaka odpowiedź powinna zostać przesłana po przepłynięciu żądania za pośrednictwem aplikacji?

Odpowiedz

3

W odniesieniu do pierwszego pytania, tak, jesteś na dobrej drodze. (Chociaż chciałbym również zobaczyć alternatywne sposoby rozwiązania tego problemu).

Jedną z sugestii, jaką mam, jest zaizolowanie aktora persister w ogóle nie wiedząc o żądaniach. Możesz przekazać żądanie jako typ Any. Twój moduł dopasowujący w kodzie serwisowym może automatycznie ponownie przesłać plik cookie do formatu Request.

case class SchedulePersisted(businessObjectId: String, cookie: Any) 

// in your actor 
override def receive = super.receive orElse { 
    case SchedulePersisted(businessObjectId, request: Request) => 
    request.complete("/businessObject/%s".format(businessObjectId)) 
} 

Jeśli chodzi o twoje drugie pytanie, klasy aktorskie tak naprawdę nie różnią się od zwykłych zajęć. Ale musisz upewnić się, że nazywasz metodę superklasy, receive, aby mogła ona obsługiwać własne wiadomości. Miałem kilka innych sposobów na osiągnięcie tego in my original answer, ale myślę, że wolą chaining partial functions like this:

class SpecialHttpService extends HttpService { 
    override def receive = super.receive orElse { 
    case SpecialMessage(x) => 
     // handle special message 
    } 
} 
0

Można także użyć dyrektywy produkcji. To pozwala na oddzielenie rzeczywistej rozrządowych z zakończenia żądanie:

get { 
    produce(instanceOf[Person]) { personCompleter => 
    databaseActor ! ShowPersonJob(personCompleter) 
    } 
} 

Dyrektywa produkować w tym przykładzie wydobywa się funkcja Person => Moduły, które można użyć do wykonania żądania przejrzysty głęboko w warstwie logiki biznesowej, która nie powinien wiedzieć o rozpylaniu.

https://github.com/spray/spray/wiki/Marshalling-Unmarshalling

Powiązane problemy