2015-11-20 4 views
1

У меня в моей системе два актера. Разговорчик. Разговор состоит из двух говорящих (к настоящему времени). Когда Talker хочет присоединиться к разговору, я должен проверить, существует ли разговор (другой говорящий создал его), а если нет, создайте его. У меня есть этот код в методе моего Talker актера:Получить или создать ребенка-актера по 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

Определение наличия актер не может быть выполнен синхронно. Таким образом, у вас есть несколько вариантов. Первые два являются более концептуальными в природе, чтобы проиллюстрировать выполнение асинхронных поисков, но я предлагаю им больше для справки об асинхронной природе актеров. В-третьих, вероятно, правильный способ делать вещи:

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

В значительной степени то же самое, что и выше, но теперь вы отправляете будущее обратно действующему актеру, чтобы разрешенный актер мог быть альт с в контексте 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 «Ed сообщения и создает/разрешает ребенку и пересылает сообщение

Я говорю, что это, скорее всего, правильный путь, так как ваша цель, кажется, дешевый поиск по определенному именованному пути. Пример, который вы приводите, предполагает предположение, что функция всегда вызывается изнутри субъекта по пути /user/conversation, иначе 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

спасибо Арне! я узнал больше, чтобы прочитать ваш ответ, чем в дневном поиске по теме! – SergiGP

+0

Еще один вопрос Arne. К настоящему времени я не делаю кластеризации, поэтому поиск легко, все в одной машине. Будет ли это влиять на процесс поиска, который делает маршрутизатор, когда я это сделаю? Или с прозрачностью местоположения все будет нормально работать магически? – SergiGP

+0

Короткий ответ - да, но, вероятно, не так, как вам хотелось бы. Во-первых, по умолчанию дети роутера будут находиться на том же узле, что и сам маршрутизатор (это может быть изменено), для другого вам понадобится способ найти узел с маршрутизатором (теперь он находится в '{address} :/user/conversation'. В кластере вам, вероятно, будет лучше всего использовать реализацию вашего маршрутизатора поверх Cluster Sharding (http://doc.akka.io/docs/akka/snapshot/scala/cluster-sharding .html) –

Смежные вопросы