Akka兄弟姐妹递归ActorRef要求

时间:2016-12-16 11:14:24

标签: scala design-patterns akka actor service-discovery

请允许我抽象我经常与演员面对的问题,以及解决它的常见设计模式。假设我有以下actor层次结构:

── user
    └── ServiceActor
        ├── TicketManager
        │   ├── WorkerA
        │   ├── WorkerB
        │   └── WorkerC
        └── UserManager
            ├── WorkerA
            ├── WorkerB
            └── WorkerC

超出第一级的层次结构,即票证和用户管理器并不重要。我要求TicketManager向UserManager发送消息,反之亦然。 (请注意,这些只是我使用的示例名称,并不代表任何实际问题)

以下哪项最合适?

选项1:

TicketManager构造函数将UserManager作为构造函数参数。 UserManager构造函数将TicketManager作为构造函数参数。 但是,两者都不能互相进行递归调用,所以无论如何这可能都不可能。

选项2:

UserManager通过actorSelection引用TicketManager作为actorSelection(" ../ TicketManager")等等,或者在启动时只保留一次并保留它。

选项3:

在任何给定的时间点,UserManager / TicketManager要求其父(ServiceActor)提供对其他兄弟的引用,并且适当地使用actorRef回复,因为父级拥有该引用。

选项4:

这应该永远不是这种情况吗?我不应该想到兄弟姐妹互相交谈,因为它会使兄弟姐妹感到困惑。以某种方式设计?是否应该有更像树的层次结构?什么是常见的设计模式,如果是这种情况可能有助于避免它?

我希望我已明确要求,请允许我在需要时进一步澄清?

2 个答案:

答案 0 :(得分:2)

如果依赖变得复杂,选项4可能是最好的方法,但是对于两个actor你可以简单地使用一个消息来初始化它们:actor被设计为保持可变状态,所以如果你改变它就没关系。

所以我会说负责创建TickerManager和ActorManager的父actor可以执行以下操作

case class InitializeTicketManager(userManager:ActorRef)

case class InitializeUserManager(ticketManager:ActorRef)
val userManager = context.actorOf(...)
val ticketManager = context.actorOf(...)
userManager ! InitializeUserManager(ticketManager)
ticketManager ! InitializeTicketManager(userManager)

答案 1 :(得分:1)

如果您使用多个ActorSystem,那么您所看到的将是某种ServiceDiscovery。这些也有帮助(Cluster Routing,Cluster Sharding,ClusterRouterGroup)。

您的工作更简单,只有一个ActorSystem。我会使用Option1来加入lazy vals和call-by-names,即

package sumnulu

import akka.actor.{Actor, ActorLogging, ActorRef, Props}

/**
  * Created by sumnulu
  */
class RootActor extends Actor with ActorLogging {
  lazy val serviceA: ActorRef = context.actorOf(ServiceA.props(serviceB))
  lazy val serviceB: ActorRef = context.actorOf(ServiceB.props(serviceA))

  override def preStart() = {
    log info "Root Actor Ready"
    serviceA ! "test"
  }

  override def receive = Actor.ignoringBehavior
}

class ServiceA(serviceB: ActorRef) extends Actor {
  println("serviceA constructor")
  serviceB ! "Hi"


  override def receive: Receive = {
    case x => println(s"self:$self from:$sender msg:$x")
  }
}


class ServiceB(serviceA: ActorRef) extends Actor {
  println("serviceB constructor")
  serviceA ! "Hi"

  override def receive: Receive = {
    case x => println(s"self:$self from:$sender msg:$x")
  }

}

//call-by-name important is i.e `=>` 
object ServiceA {
  def props(serviceB: => ActorRef) = Props(new ServiceA(serviceB))
}

//call-by-name important is i.e `=>` 
object ServiceB {
  def props(serviceA: => ActorRef) = Props(new ServiceB(serviceA))
}

object RootActor {
  def props = Props[RootActor]
}
[info] [INFO] [02/18/2017 17:49:52.485] [Main-akka.actor.default-dispatcher-4] [akka://Main/user/app] Root Actor Ready
[info] serviceA constructor
[info] serviceB constructor
[info] self:Actor[akka://Main/user/app/$a#1420483478] from:Actor[akka://Main/user/app#274353135] msg:test
[info] self:Actor[akka://Main/user/app/$b#1573183992] from:Actor[akka://Main/user/app/$a#1420483478] msg:Hi
[info] self:Actor[akka://Main/user/app/$a#1420483478] from:Actor[akka://Main/user/app/$b#1573183992] msg:Hi