通过 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,等等。