2013-04-06 11 views
5

Chciałbym propagować kontekst żądania bezpośrednio w systemie współpracujących aktorów.Przekazywanie kontekstu żądania bezwarunkowo w systemie aktorów

Aby uprościć i przedstawić sytuację, mój system ma wielu aktorów, a komunikaty przekazywane do tych aktorów muszą zawierać ten obiekt RequestContext.

ActorA odbiera komunikaty typu MessageA ActorB odbiera komunikaty typu MessageB

gdy ActorA musi wysłać wiadomość do ActorB, w ramach rozpatrywania MessageA, wykonuje logikę biznesową, a następnie konstruuje MessageB od wyniki logiki, jak również RequestContext dostępnych w MessageA a następnie wysyła go do ActorB

def handle(ma:MessageA) { 
val intermediateResult = businessLogic(ma) 
actorB ! MessageB(intermediateResult, ma.requestContext) 
} 

Mamy zabił wiadomości mają być przetwarzane i wyraźnie przechodząc wokół RequestContext jest kłopotliwe.

Próbuję twórczych sposobów korzystania z funkcji implicite Scala, aby uniknąć jawnego wstrzyknięcia RequestContext osadzonego w wiadomości przychodzącej do wiadomości wychodzącej.

Komunikaty są klasami przypadków (i muszą być). Czytałem o regułach implicits, ale wprowadzenie atrybutów obiektu do bieżącego domyślnego zakresu wydaje się daleko idące.

To, jestem pewien, powinno być powszechnym wymogiem. Jakieś sugestie?

Dzięki.

Odpowiedz

6

Moim zdaniem, najprostszym sposobem jest, aby Państwa val ukryte w klasie przypadku.

case class MessageA(req: RequestA)(implicit val ctx: RequestContext) 

case class MessageB(req: RequestB)(implicit val ctx: RequestContext) 

def businessLogic(req:RequestA):RequestB 


def handle(ma: MessageA): Unit = { 
    // import all the members of ma so that there is a legal implicit RequestContext in scope 
    import ma._ 
    val intermediateResult = businessLogic(req) 
    actorB ! MessageB(intermediateResult) 
} 
+0

Dzięki za odpowiedź. Poważnie zastanawiałem się nad tym podejściem, ale zrezygnowałem z myślenia, że ​​przekazanie tego parametru jest znacznie bardziej czytelne i mniej szczegółowe niż dodanie wszystkich importów i implicite w celu korzystania z funkcji - nie sądzisz? – vishr

+0

Jeśli to jest problem, odfiltrowałbym procesor wiadomości, który jest MessageA => MessageB i po prostu wywołaj go w ciele aktora. To również zwiększyłoby testowalność – Edmondo1984

6

W przykładzie Twój obsługa wiadomości w pytaniu jest zbitek do metody już, co czyni to prosta:

trait RequestContext 

case class MessageA(req: RequestA, ctx: RequestContext) 
object MessageA { 
    def apply(req: RequestA)(implicit ctx: RequestContext) = MessageA(req, ctx) 
} 

case class MessageB(req: RequestB, ctx: RequestContext) 
object MessageB { 
    def apply(req: RequestB)(implicit ctx: RequestContext) = MessageB(req, ctx) 
} 

class Example extends Actor { 

    def receive = { 
    case MessageA(req, ctx) => handle(req)(ctx) 
    } 

    def handle(req: RequestA)(implicit ctx: RequestContext): Unit = { 
    val intermediateResult = businessLogic(req) // could take implicit ctx as well 
    actorB ! MessageB(intermediateResult) 
    } 
} 

Ale jak widać nie jest jeszcze pewne napowietrznych podczas deklarowania typy wiadomości i podpis metody handle również muszą zostać zmienione. To, czy ten system jest tego warty, zależy od stosunku między konsumentami a producentami tych niejawnych wartości (tj. Jeśli więcej niż jedna rzecz w handle używa kontekstu, ma to więcej sensu).

Odmianą powyżej mogą być:

case class MessageA(req: RequestA, ctx: RequestContext) 
object MessageA { 
    def apply(req: RequestA)(implicit ctx: RequestContext) = MessageA(req, ctx) 
    implicit def toContext(implicit msg: MessageA) = msg.ctx 
} 

case class MessageB(req: RequestB, ctx: RequestContext) 
object MessageB { 
    def apply(req: RequestB)(implicit ctx: RequestContext) = MessageB(req, ctx) 
    implicit def toContext(implicit msg: MessageB) = msg.ctx 
} 

... 
def handle(implicit ma: MessageA): Unit = { 
    val intermediateResult = businessLogic(req) 
    actorB ! MessageB(intermediateResult) 
} 
+0

dlaczego ukryte przesłanie? nie jest prośba, która powinna być domniemana? patrz moja odpowiedź – Edmondo1984

+0

@Roland, Twój pomysł zainspirowany schludniejszy podejście. Jeśli MessageA i MessageB odziedziczą po AbstractMessage, a my dostarczymy niejawną konwersję z AbstractMessage do RequestContext w obiekcie towarzyszącym AbstractMessage, problem zostanie rozwiązany! Wszystko, co trzeba zrobić, aby każda wiadomość jest zadeklarować niejawny żądania kontekstowe param, i tak długo jak jest ona dostępna, zostanie on przekazany wraz! – vishr

+0

Cóż ... Byłem podekscytowany ... okazało się, że oznaczenie każdej metody obsługi z niejawnym parametrem MessageA jest konieczne, aby to zadziałało, ale nie czyni tego czytelnym. Niejawne deklaracje propagują zbyt wiele w stosunku do kodu logiki biznesowej. – vishr

0

Utwórz ogólną klasę obwiedni zawierającą zarówno oryginalną wiadomość, jak i kontekst. Utwórz cechę rozszerzającą Actor (musisz umieścić ją w pakiecie akka._). Zastąpić metodę aroundReceive(), która rozpakowuje kopertę, inicjuje zmienną chronioną aktora do przechowywania bieżącego kontekstu, wywołuje oryginalną metodę odbierania z rozpakowaną wiadomością, odinicjalizuje zmienną kontekstową. Raczej wątpliwe podejście, ale tak właśnie zachowuje się Actor.sender().

Aby wysłać wiadomość do takiego kontekstowego aktora, musisz zawinąć wiadomość do koperty wspomnianej powyżej ręcznie lub możesz zautomatyzować to zadanie, wprowadzając rozszerzenie ponad ActorRef, które implementuje operacje tell/ask przez podniesienie wiadomości do kontekstu.

To nie jest tylko pojęciem, ale krótkie podsumowanie podejścia Niedawno próbował z sukcesem. Co więcej, uogólniłem go jeszcze bardziej, wprowadzając kolejną abstrakcyjną koncepcję - niejawne środowiska kontekstowe, takie jak oparte na aktorach, oparte na ThreadLocal, oparte na przyszłości itp., Co umożliwiło mi bezkrytyczne przekazywanie danych kontekstowych w całym łańcuchu operacji synchronizacji/asynchronizacji.

Powiązane problemy