2015-11-20 64 views
1

我的系統中有兩名演員。談話者和談話。談話由兩個談話者組成(到目前爲止)。當說話者想要加入對話時,我應該檢查對話是否存在(另一個說話者已經創建了它),如果不存在,請創建它。我有這樣的代碼在我發話演員的方法:通過ID獲取或創建小孩演員

def getOrCreateConversation(conversationId: UUID): ActorRef = { 

    // @TODO try to get conversation actor by conversationId 
    context.actorSelection("user/conversation/" + conversationId.toString) 

    // @TODO if it not exists... create it 
    context.actorOf(Conversation.props(conversationId), conversationId.toString) 
    } 

正如你所看到的,當我創建我的converastion演員與actorOf我傳遞的第二個參數的conversationId。我這樣做是爲了方便搜索這位演員......這是做到這一點的正確方法嗎?

謝謝

編輯

感謝@Arne我終於做到了這一點:

class ConversationRouter extends Actor with ActorLogging { 
    def receive = { 
    case ConversationEnv(conversationId, msg) => 
     val conversation = findConversation(conversationId) match { 
     case None => createNewConversation(conversationId) 
     case Some(x) => x 
     } 
     conversation forward msg 
    } 

    def findConversation(conversationId: UUID): Option[ActorRef] = context.child(conversationId.toString) 

    def createNewConversation(conversationId: UUID): ActorRef = { 
    context.actorOf(Conversation.props(conversationId), conversationId.toString) 
    } 
} 

而且測試:

class ConversationRouterSpec extends ChatUnitTestCase("ConversationRouterSpec") { 

    trait ConversationRouterSpecHelper { 
    val conversationId = UUID.randomUUID() 

    var newConversationCreated = false 

    def conversationRouterWithConversation(existingConversation: Option[ActorRef]) = { 
     val conversationRouterRef = TestActorRef(new ConversationRouter { 
     override def findConversation(conversationId: UUID) = existingConversation 

     override def createNewConversation(conversationId: UUID) = { 
      newConversationCreated = true 
      TestProbe().ref 
     } 
     }) 
     conversationRouterRef 
    } 
    } 

    "ConversationRouter" should { 
    "create a new conversation when a talker join it" in new ConversationRouterSpecHelper { 
     val nonExistingConversationOption = None 
     val conversationRouterRef = conversationRouterWithConversation(nonExistingConversationOption) 

     conversationRouterRef ! ConversationEnv(conversationId, Join(conversationId)) 

     newConversationCreated should be(right = true) 
    } 

    "not create a new conversation if it already exists" in new ConversationRouterSpecHelper { 
     val existingConversation = Option(TestProbe().ref) 
     val conversationRouterRef = conversationRouterWithConversation(existingConversation) 

     conversationRouterRef ! ConversationEnv(conversationId, Join(conversationId)) 

     newConversationCreated should be(right = false) 
    } 
    } 
} 

回答

6

確定是否存在一個演員不能同步完成。所以你有幾個選擇。前兩個在本質上更具概念性,以說明異步查找,但我更多地提供它們以供參考有關actor的異步本質。三是有可能的處事正確方式:

1.函數返回一個Future[ActorRef]

def getOrCreateConversation(conversationId: UUID): Unit { 
    context.actorSelection(s"user/conversation/$conversationId") 
    .resolveOne() 
    .recover { case _:Exception => 
     context.actorOf(Conversation.props(conversationId),conversationId.toString) 
     } 
} 

2.它Unit,並將它發送ActorRef回當前的演員

和上面幾乎一樣,但是現在我們把未來管回給當前的演員,這樣解決的演員可以是de ALT與調用演員的receive環的情況下:

def getOrCreateConversation(conversationId: UUID): Unit { 
    context.actorSelection(s"user/conversation/$conversationId") 
    .resolveOne() 
    .recover { case _:Exception => 
     context.actorOf(Conversation.props(conversationId),conversationId.toString) 
     }.pipeTo(self) 
} 

3.創建您發送Id「編報文到路由器演員和它創建/解決了孩子,並轉發該消息

我說這可能是正確的方法,因爲你的目標似乎是在特定的命名路徑上查找廉價。您給出的示例假定該函數始終在路徑/user/conversation的actor中調用,否則context.actorOf將不會在/user/conversation/{id}/處創建子項。

也就是說,你手上有一個路由器模式,並且你創建的孩子已經被其子集合中的路由器所知曉。這種模式假定你周圍的任何交談消息的信封,是這樣的:

case class ConversationEnv(id: UUID, msg: Any) 

現在所有談話的消息被髮送到路由器,而不是給孩子談話直接。路由器現在可以查找子其子集在:

def receive = { 
    case ConversationEnv(id,msg) => 
    val conversation = context.child(id.toString) match { 
     case None => context.actorOf(Conversation.props(id),id.toString) 
     case Some(x) => x 
    } 
    conversation forward msg 
} 

額外的好處是,你的路由器也是談話的主管,因此,如果談話兒童死亡,它可以對付它。不要將孩子ActorRef暴露給外界也有好處,你可以讓它在閒置時死掉,並讓它在下一個消息收據上重新創建。

+0

謝謝Arne!我已經學會了更多的閱讀你的答案,而不是在一天中搜索這個主題! – SergiGP

+0

另一個問題Arne。現在我沒有進行集羣化,所以查找很容易,一切都在同一臺機器上。它會影響到路由器在執行時的查找過程嗎?或者與位置透明度一切都會神奇地正常工作? – SergiGP

+0

簡短的回答是肯定的,但可能不是你想要的。首先,默認情況下,路由器的子節點都與路由器本身在同一個節點上(這可能會改變),對於另一個需要使用路由器查找節點的方法(現在位於'{address} :/ user/conversation'。在一個集羣中,你最好的方法是在集羣分片之上使用你的路由器(http://doc.akka.io/docs/akka/snapshot/scala/cluster-sharding的.html) –