Scala Akka TypedActor与编写自己的Actor类静态接口

Scala Akka TypedActor与编写自己的Actor类静态接口,scala,akka,actor,typedactor,Scala,Akka,Actor,Typedactor,我已经使用Akka和Scala大约一个月了,我有点担心用消息替换显式接口。考虑以下简单的Akka演员: case class DoMyHomework() class Parent extends Actor { def receive = { case d: DoMyHomework => // do nothing } } 参与者或非参与者代码,向该参与者发送DoMyHomework消息,如下所示: ActorRef parent = ... parent.ask(D

我已经使用Akka和Scala大约一个月了,我有点担心用消息替换显式接口。考虑以下简单的Akka演员:

case class DoMyHomework()
class Parent extends Actor {
  def receive = {
    case d: DoMyHomework => // do nothing
  }
}
参与者或非参与者代码,向该参与者发送DoMyHomework消息,如下所示:

ActorRef parent = ...
parent.ask(DoMyHomework)
object Parent {
  /**
   * Send this message to make your parent do your homework … yeah, right ;-)
   */
  case object DoHomework
}

/**
 * This actor will do your homework if asked to.
 * 
 * ==Actor Contract==
 * 
 * ===Inbound Messages===
 *  - '''DoHomework''' will ask to do the homework
 * 
 * ===Outbound Messages===
 *  - '''HomeworkResult''' is sent as reply to the '''DoHomework''' request
 * 
 * ===Failure Modes===
 *  - '''BusinessTripException''' if the parent was not home
 *  - '''GrumpyException''' if the parent thinks you should do your own homework
 */
class Parent extends Actor {
  …
}
不知道结果会是什么。答案的类型是什么?我会得到答案吗?我能得到一个例外吗?等等

解决方案似乎是记录案例类。。。但如果其他参与者也收到了相同的案例类呢。然后,接收该消息的文档应该在actor本身中

为了稍微清理一下,我想到了以下几点:

trait SomeoneSmarter {
  def wouldYouDoMyHomework: Future[Boolean] 
}
class Parent extends Actor with SomeoneSmarter {
  case class DoMyHomework()
  def wouldYouDoMyHomework = {
    (self ? DoMyHomework()).mapTo(Boolean)
  }
  def receive = {
    case d: DoMyHomework =>
      // TODO: If I'm busy schedule a false "No way" reply for a few seconds from now.
      // Just to keep their hopes up for a while. Otherwise, say sure right away.
  }
}
trait SomeoneSmarter {

  def wouldYouDoMyHomework : Boolean 

}

class Response()
case class NoWay() extends Response
case class Sure() extends Response

class ActorNetworkFrontEnd extends Actor {

  def receive = {
    case d: DoMyHomework =>
      busy match {
        case true => sender ! NoWay()
        case false => sender ! Sure()
      }
  }
}

case class SomeoneSmarter(actorNetworkFrontEnd:ActorRef) extends SomeoneSmarter {

  def wouldYouDoMyHomework : Boolean = {
    val future = actorNetworkFrontEnd ? DoMyHomework()
    val response = Await.result(future, timeout.duration).asInstanceOf[Response]
    response match {
      case NoWay() => false
      case Sure() => true
    }
  }

}
所以,我和同事们聊了聊,其中一个反应是“你没有忠于演员模型。”

首先,我非常感谢长期使用演员的人们的指导。所有的信息都变得难以处理了吗?您是否最终隐藏了在接口后面传递的消息

我提议的演员仍然可以选择在他们之间发送消息,订阅事件流,所有你期望从Akka得到的东西。这个界面为你提供了一种经过时间考验的方式来了解你在说什么。在IDE中进行编码时,它会有所帮助,等等。为什么一个参与者的用户需要知道它是一个参与者(除非它也是一个参与者并且与之紧密结合)

我得到的另一个反应是“看起来你想要一个打字机”。但在读了TypedActor之后,我不相信。当然,TypedActor为我省去了创建这些内部消息的麻烦。但是,至少从 我得到的印象是,TypedActor只是用来作为一个代理来处理您想要封装的代码块,或者使线程安全,或者干脆不直接从当前线程调用。您编写的只是实现和接口。您不会弄乱参与者本身(代理)——例如,如果您希望实现执行定期工作或订阅事件流,或者执行与接口无关的任何其他操作

我也读过,没有发现这个例子更有启发性。我可能只是不是在摸索TypedActor(并不是说我已经真正了解了演员)

提前谢谢你的帮助


皮诺

免责声明:我不是阿克卡/演员专家。我已经和演员和Akka一起工作了18个月了,我仍然在努力思考某些概念,尤其是在不使用Akka的时候

对于您想知道阿克卡期货的回报类型的特殊且狭义的情况,是的,您应该使用TypedActor。在我使用TypedActor的几次中,它们被用来为Actor系统之外的模块提供API。也就是说,我在Akka上构建了一个系统,它在Akka网络内完成了大部分工作,但在Akka网络外有一个或两个模块,需要访问Akka网络提供的功能。最值得注意的是Scalatra前端,它调用Akka网络,并在响应其客户机之前对Akka网络返回的值进行一些处理。然而,TypedActor实际上只是Akka网络的前端。我将使用TypedActor作为外部(Akka网络外部)模块的API前端,作为另一种关注点分离

总的来说,我同意那些告诉你“你没有忠实于演员模型”的人试图强制其返回类型的视图。在最纯粹的形式中,也是我最成功的方式中,Actor模型是使用fire-and-forget语义实现的。这些消息不会变得笨拙,在许多情况下,它们帮助组织了我的代码并定义了工作边界。把它们放在自己的包里确实有帮助

如果我要实现您描述的功能,它将如下所示:

trait SomeoneSmarter {
  def wouldYouDoMyHomework: Future[Boolean] 
}
class Parent extends Actor with SomeoneSmarter {
  case class DoMyHomework()
  def wouldYouDoMyHomework = {
    (self ? DoMyHomework()).mapTo(Boolean)
  }
  def receive = {
    case d: DoMyHomework =>
      // TODO: If I'm busy schedule a false "No way" reply for a few seconds from now.
      // Just to keep their hopes up for a while. Otherwise, say sure right away.
  }
}
trait SomeoneSmarter {

  def wouldYouDoMyHomework : Boolean 

}

class Response()
case class NoWay() extends Response
case class Sure() extends Response

class ActorNetworkFrontEnd extends Actor {

  def receive = {
    case d: DoMyHomework =>
      busy match {
        case true => sender ! NoWay()
        case false => sender ! Sure()
      }
  }
}

case class SomeoneSmarter(actorNetworkFrontEnd:ActorRef) extends SomeoneSmarter {

  def wouldYouDoMyHomework : Boolean = {
    val future = actorNetworkFrontEnd ? DoMyHomework()
    val response = Await.result(future, timeout.duration).asInstanceOf[Response]
    response match {
      case NoWay() => false
      case Sure() => true
    }
  }

}
请记住,我写的方式会影响你的家庭作业,它会在等待答案时阻塞。然而,有一些聪明的方法可以异步完成这项工作。有关更多信息,请参阅

此外,请记住,一旦您的消息进入Akka网络,您就可以完成所有很酷的缩放和远程处理工作,TypedActor API的用户永远不必知道

这样做确实在大型项目中增加了一些复杂性,但是如果您认为它是将API提供给外部模块的责任分离,甚至可能将责任转移到另一个包,那么它很容易管理。


好问题。我迫不及待地想听到更有经验的Akka开发人员的回答。

计算的参与者模型与面向对象编程有着巨大的相似性。OO是关于控制权的间接转移。当您调用方法(发送消息)时,您将失去对消息的控制。当然,静态编程语言在所有类型检查方面都有一点帮助,但除此之外,您不知道消息会发生什么。见鬼,也许这个方法永远不会返回,尽管返回类型清楚地表明它会返回(抛出异常、活动锁,你可以命名它…)!同意,当您习惯Java甚至更好的Scala时,放弃静态类型很糟糕,但这并不是说您没有任何好处。动态类型为您提供松散耦合。例如,不需要创建额外的接口来为测试引入模拟参与者
ActorRef
是您唯一需要的API。

Actor封装 让我首先回应一点,我认为这是非常重要的。你说:

为什么一个参与者的用户需要知道它是一个参与者(除非它也是一个参与者并且与之紧密结合)

actor是一种与传统OO截然不同的编程范式,主要区别在于一切都是异步的,因此永远不存在真正的“返回值”。这意味着它通常是一个坏id