我的系统中有两个演员。说话者和谈话。对话包括两个讲话者(现在)。当Talker想要加入对话时,我应该检查对话是否存在(另一个说话者已经创建了对话),如果不存在,则创建它。我在我的发言人演员的方法中有这个代码:
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)
}
正如你所看到的,当我用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)
}
}
}
答案 0 :(得分:8)
确定演员的存在不能同步完成。所以你有几个选择。前两个在本质上更具概念性,用于说明进行异步查找,但我提供了更多关于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
发送回当前演员
与上面几乎完全相同,但是现在我们将未来传递回当前的actor,以便可以在调用actor的receive
循环的上下文中处理已解析的actor:
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
暴露给外界也有一个好处,就是你可以让它在闲置时死去,并在下一个消息收据上重新创建等等。