按ID获取或创建子actor

时间:2015-11-20 15:22:23

标签: scala akka

我的系统中有两个演员。说话者和谈话。对话包括两个讲话者(现在)。当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)
    }
  }
}

1 个答案:

答案 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暴露给外界也有一个好处,就是你可以让它在闲置时死去,并在下一个消息收据上重新创建等等。