2014-07-11 12 views
7

Domanda:modello corretto per accumulare stato in un attore Akka

Qual è il modello corretto per accumulare stato in un attore Akka?

Contesto:

Diciamo che ho un paio di servizi che tutti i dati di ritorno.

class ServiceA extends Actor { 
    def receive = { 
    case _ => sender ! AResponse(100) 
    } 
} 

class ServiceB extends Actor { 
    def receive = { 
    case _ => sender ! BResponse("n") 
    } 
} 

// ... 

voglio avere un controllo/supervisione attore che coordina a parlare con tutti questi servizi e tenere traccia delle loro risposte, poi l'invio di una risposta con tutti i dati indietro al mittente originale.

class Supervisor extends Actor { 
    def receive = { 
    case "begin" => begin 
    case AResponse(id) => ??? 
    case BResponse(letter) => ??? 
    } 

// end goal: 
def gotEverything(id: Int, letter: String) = 
    originalSender ! (id, letter) 

    def begin = { 
    ServiceA ! "a" 
    ServiceB ! "b" 
    } 
} 

All'inizio delle risposte di servizio, come posso mantenere tutto questo stato associato? A quanto ho capito, se dovessi assegnare il valore di AResponse a, diciamo, var aResponse: Int, quella variabile cambierà costantemente mentre vengono ricevuti messaggi diversi e non è possibile che io conti su quello var rimanendo mentre aspetto il messaggio BResponse .

Mi rendo conto che potrei usare ask e solo nidificare/flatMap Future, ma da quello che ho letto è un cattivo schema. C'è un modo per ottenere tutto questo senza Future?

+0

Perché "aResponse' cambia più di una volta? Si invia solo un messaggio a 'ServiceA'. Qual è il tuo obiettivo esattamente? Aspetta di ricevere 'AResponse' e' BResponse' e ​​chiama 'gotEverything' con i loro valori? – vptheron

+0

>> Qual è il tuo obiettivo esattamente?Aspetta di ricevere AResponse e BResponse e chiama GotTutto con i loro valori? SI –

+0

puoi memorizzare le risposte in una lista con qualche identificatore o attore ref – wedens

risposta

15

Poiché gli attori non sono mai accessibili da più thread contemporaneamente, è possibile memorizzare e modificare facilmente qualsiasi stato in essi desiderato. Ad esempio, è possibile effettuare questa operazione:

class Supervisor extends Actor { 
    private var originalSender: Option[ActorRef] = None 
    private var id: Option[WhateverId] = None 
    private var letter: Option[WhateverLetter] = None 

    def everythingReceived = id.isDefined && letter.isDefined 

    def receive = { 
    case "begin" => 
     this.originalSender = Some(sender) 
     begin() 

    case AResponse(id) => 
     this.id = Some(id) 
     if (everythingReceived) gotEverything() 

    case BResponse(letter) => 
     this.letter = Some(letter) 
     if (everythingReceived) gotEverything() 
    } 

    // end goal: 
    def gotEverything(): Unit = { 
    originalSender.foreach(_ ! (id.get, letter.get)) 
    originalSender = None 
    id = None 
    letter = None 
    } 

    def begin(): Unit = { 
    ServiceA ! "a" 
    ServiceB ! "b" 
    } 
} 

C'è un modo migliore, tuttavia. È possibile emulare un tipo di macchina di stato con attori senza variabili di stato esplicite. Questo viene fatto usando il meccanismo become().

class Supervisor extends Actor { 
    def receive = empty 

    def empty: Receive = { 
    case "begin" => 
     AService ! "a" 
     BService ! "b" 
     context become noResponses(sender) 
    } 

    def noResponses(originalSender: ActorRef): Receive = { 
    case AResponse(id) => context become receivedId(originalSender, id) 
    case BResponse(letter) => context become receivedLetter(originalSender, letter) 
    } 

    def receivedId(originalSender: ActorRef, id: WhateverId): Receive = { 
    case AResponse(id) => context become receivedId(originalSender, id) 
    case BResponse(letter) => gotEverything(originalSender, id, letter) 
    } 

    def receivedLetter(originalSender: ActorRef, letter: WhateverLetter): Receive = { 
    case AResponse(id) => gotEverything(originalSender, id, letter) 
    case BResponse(letter) => context become receivedLetter(originalSender, letter) 
    } 

    // end goal: 
    def gotEverything(originalSender: ActorRef, id: Int, letter: String): Unit = { 
    originalSender ! (id, letter) 
    context become empty 
    } 
} 

Questo può essere leggermente più dettagliato, ma non contiene variabili esplicite; tutto lo stato è implicitamente contenuto nei parametri dei metodi Receive e quando questo stato deve essere aggiornato, la funzione di ricezione dell'attore viene semplicemente cambiata per riflettere questo nuovo stato.

Nota che il codice sopra riportato è molto semplice e non funzionerà correttamente quando possono esserci molti "mittenti originali". In tal caso dovrai aggiungere un id a tutti i messaggi e usarli per determinare quali risposte appartengono allo stato di "mittente originale" oppure puoi creare più attori, ciascuno per ciascuno dei "mittenti originali".

1

Credo che il modo di Akka sia quello di utilizzare il modello di attore per richiesta. In questo modo, invece di capire quale risposta corrisponde a cosa, crei un nuovo attore ogni volta che ricevi una richiesta. Questo è molto economico e, infatti, accade ogni volta che lo chiedi().

Questi processori di richiesta (è così che li chiamo) di solito hanno campi semplici per le risposte. Ed è solo una questione di semplice confronto nullo per vedere se la richiesta è arrivata.

Tentativi/errori anche ottenere molto più facile con questo schema. Anche i timeout.

Problemi correlati