通过 ID 获取或创建子演员
Get or create child actor by ID
我的系统中有两个演员。谈话者和谈话。对话由两个谈话者组成(到目前为止)。当一个 Talker 想要加入对话时,我应该检查对话是否存在(另一个 talker 创建了它),如果不存在,则创建它。我的 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 创建我的会话 actor 时,我将 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)
}
}
}
无法同步确定演员的存在。所以你有几个选择。前两个在本质上更具概念性,用于说明执行异步查找,但我提供它们更多是为了参考有关参与者的异步性质。第三个可能是正确的做事方式:
1.使函数 return 成为 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
发送回您当前的演员
与上面的几乎相同,但现在我们将 future 传回当前 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。创建一个路由器 actor,您将 Id
发送的消息发送到该路由器 actor,它 creates/resolves child 并转发消息
我说这可能是正确的方法,因为您的目标似乎是在特定的命名路径上进行廉价查找。您给出的示例假设函数总是从路径 /user/conversation
的参与者内部调用,否则 context.actorOf
不会在 /user/conversation/{id}/
.[=23 创建 child =]
也就是说,您手上有一个路由器模式,并且您创建的 child 已经为路由器 child 集合中的路由器所知。此模式假定您在任何对话消息周围都有一个信封,如下所示:
case class ConversationEnv(id: UUID, msg: Any)
现在所有对话消息都发送到路由器,而不是直接发送到对话 child。路由器现在可以在其 child 集合中查找 child:
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
}
额外的好处是你的路由器也是会话监督者,所以如果会话child死了,它可以处理它。不将 child ActorRef
暴露给外界还有一个好处,你可以让它在空闲时死掉,并让它在下一次收到消息时得到 re-created,等等。
我的系统中有两个演员。谈话者和谈话。对话由两个谈话者组成(到目前为止)。当一个 Talker 想要加入对话时,我应该检查对话是否存在(另一个 talker 创建了它),如果不存在,则创建它。我的 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 创建我的会话 actor 时,我将 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)
}
}
}
无法同步确定演员的存在。所以你有几个选择。前两个在本质上更具概念性,用于说明执行异步查找,但我提供它们更多是为了参考有关参与者的异步性质。第三个可能是正确的做事方式:
1.使函数 return 成为 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
发送回您当前的演员
与上面的几乎相同,但现在我们将 future 传回当前 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。创建一个路由器 actor,您将 Id
发送的消息发送到该路由器 actor,它 creates/resolves child 并转发消息
我说这可能是正确的方法,因为您的目标似乎是在特定的命名路径上进行廉价查找。您给出的示例假设函数总是从路径 /user/conversation
的参与者内部调用,否则 context.actorOf
不会在 /user/conversation/{id}/
.[=23 创建 child =]
也就是说,您手上有一个路由器模式,并且您创建的 child 已经为路由器 child 集合中的路由器所知。此模式假定您在任何对话消息周围都有一个信封,如下所示:
case class ConversationEnv(id: UUID, msg: Any)
现在所有对话消息都发送到路由器,而不是直接发送到对话 child。路由器现在可以在其 child 集合中查找 child:
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
}
额外的好处是你的路由器也是会话监督者,所以如果会话child死了,它可以处理它。不将 child ActorRef
暴露给外界还有一个好处,你可以让它在空闲时死掉,并让它在下一次收到消息时得到 re-created,等等。