28

我已经使用 Akka 和 Scala 大约一个月了,我对用消息替换显式接口感到有些困扰。考虑以下简单的 Akka Actor:

case class DoMyHomework()
class Parent extends Actor {
  def receive = {
    case d: DoMyHomework => // do nothing
  }
}

向此参与者发送 DoMyHomework 消息的参与者或非参与者代码,如下所示:

ActorRef parent = ...
parent.ask(DoMyHomework)

不知道结果会是什么。答案的类型是什么?我会得到答案吗?我可以例外吗?等等。

修复似乎是记录案例类......但是如果其他一些演员也收到相同的案例类怎么办。那么接收该消息的文档应该在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.
  }
}

所以,我和同事聊起这件事,其中一个反应是“你不忠于演员模型”。

首先,我非常感谢长期使用 Actors 的人们提供的一些指导。所有的消息都会变得笨拙吗?您最终是否将消息传递隐藏在接口后面?

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

我得到的另一个反应是“看起来你想要一个 TypedActor”。但是在阅读了有关 TypedActor 的内容后,我并不相信。当然,TypedActor 为我省去了创建这些内部消息的麻烦。但是,至少从 http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html的代码示例中,我得到的印象是 TypedActor 仅作为一个块周围的代理工作您想要封装的代码,或使线程安全,或者根本不直接从当前线程调用。您编写的代码只是实现和接口。您不会弄乱参与者本身(代理) - 例如,如果您希望您的实现定期工作或订阅事件流,或做任何与接口无关的事情。

我还阅读了http://letitcrash.com/post/19074284309/when-to-use-typedactors并没有发现该示例更具启发性。我可能只是不了解 TypedActor(并不是说我声称已经真正了解 Actors)。

提前感谢您的帮助。

皮诺

4

3 回答 3

11

演员封装

让我先回应一个我认为非常重要的观点。你说:

为什么演员的用户需要知道它是演员(除非它也是演员并且与演员紧密耦合)?

Actors 是与传统 OO 完全不同的编程范式,主要区别在于一切都是异步的,因此永远不会有真正的“返回值”。这意味着隐藏它是演员的事实通常是一个坏主意,例外情况请参阅我的 TypedActors 博客文章。Actor 最好的一点是它们被完全封装在 Akka 后面,ActorRef而不是 OO 语言的弱封装。为了充分利用它,请ActorRef尽可能公开 s,这使客户端代码有机会以最合适的方式使用它们(可能正在使用tellask取决于上下文)。

构建您的消息

当编写一个actor时,你应该把关于这个actor的所有东西都放在一个地方,包括接口契约描述。它可能看起来像这样:

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 {
  …
}

与 TypedActor 的区别

使用普通的无类型 Actor 可以让您充分利用 Actor 模型的功能,包括动态改变行为,并且不会试图将自己限制在“同步”调用的超时保护笼子中(简而言之,TypedActor 在以下情况下最有用)使用幕后的演员实现传统的同步接口)。我同意 IDE 对消息类型的支持会很好,但这是一个工具问题(我一直在与 ScalaIDE 团队讨论添加一些魔法,但这必须等到它能够获得优先权)。将有关参与者的所有属性定义在一个地方是重要的部分。

于 2012-09-30T21:10:39.740 回答
8

免责声明:我不是 Akka/Actors 专家。我已经使用 Actors 和 Akka 大约 18 个月了,但我仍在尝试围绕某些概念进行思考,尤其是在不使用 Akka 时。

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

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

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

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
    }
  }

}

请记住我写 willYouDoMyHomework 的方式,它会在等待答案时阻塞。但是,有一些聪明的方法可以异步执行此操作。有关更多信息,请参阅http://doc.akka.io/docs/akka/2.0.3/scala/futures.html

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

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

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

于 2012-09-21T13:01:53.333 回答
1

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

于 2012-09-21T15:57:46.173 回答