基于Actor的Web服务 - 如何正确完成?

时间:2012-02-10 10:49:54

标签: scala actor akka spray

在过去的几个月里,我和我的同事们成功构建了一个服务器端系统,用于向iPhone设备发送推送通知。基本上,用户通过RESTful webservice(Spray-Server注册这些通知,最近更新为使用Spray-can作为HTTP层),并且逻辑使用Akka调度一个或多个消息以便将来发送。调度器。

这个系统,正如我们构建的那样,简单地工作:它可以每秒处理数百甚至数千个HTTP请求,并且可以以每秒23,000的速率发送通知 - 如果我们减少日志输出,可能会更多,添加多个通知发送方actor(以及与Apple的更多连接),并且可能在我们使用的Java库中进行一些优化(java-apns)。

这个问题是关于如何正确行事(tm)。我的同事,更了解Scala和基于演员的系统,注意到应用程序不是一个“纯粹的”基于演员的系统 - 他是对的。我现在想知道的是如何正确地做到这一点。

目前,我们有一个Spray HttpService actor,而不是子类,它是用一组指令来初始化的,这些指令概述了我们的HTTP服务逻辑。目前,非常简化,我们有这样的指令:

post {
  content(as[SomeBusinessObject]) { businessObject => request =>
    // store the business object in a MongoDB back-end and wait for the ID to be
    // returned; we want to send this back to the user.
    val businessObjectId = persister !! new PersistSchedule(businessObject)
    request.complete("/businessObject/%s".format(businessObjectId))
  }
}

现在,如果我做对了,那么来自演员的'等待响应'是基于演员的编程中的禁忌(加上!!已弃用)。我认为“正确”的方法是将request对象传递给消息中的persister actor,并在收到消息后立即调用request.complete从后端生成ID。

我已经在我的应用程序中重写了其中一条路线来做这件事;在发送给actor的消息中,还发送请求对象/引用。这看起来像是应该的:

  content(as[SomeBusinessObject]) { businessObject => request =>
    persister ! new PersistSchedule(request, businessObject)
  }

我主要担心的是,我们似乎将request对象传递给了'业务逻辑',在本例中是persister。持久性现在负有额外的责任,即调用request.complete,以及有关它运行的系统的知识,即它是Web服务的一部分。

处理这种情况的正确方法是什么,以便persister actor不会意识到它是http服务的一部分,并且不需要知道如何输出生成的ID?

我认为该请求仍应传递给persister actor,但不是调用request.complete的persister actor,而是将消息发送回HttpService actor(SchedulePersisted(request, businessObjectId)消息),只需拨打request.complete("/businessObject/%s".format(businessObjectId))即可。基本上是:

def receive = {
  case SchedulePersisted(request, businessObjectId) =>
    request.complete("/businessObject/%s".format(businessObjectId))
}

val directives = post {
  content(as[SomeBusinessObject]) { businessObject => request =>
    persister ! new PersistSchedule(request, businessObject)
  }
}

我采用这种方法走在正确的轨道上吗?

一个较小的辅助spray-server特定问题,是否可以继承HttpService并覆盖接收方法,或者我会以这种方式破坏它? (我对子类化actor没有任何线索,或者如何将无法识别的消息传递给'父'参与者)

最后一个问题,是将request对象/引用传递给可能通过整个应用程序的演员消息中的一个好的方法,还是有更好的方法来“记住”应该在发送响应之后发送什么请求通过申请流动请求?

2 个答案:

答案 0 :(得分:3)

关于你的第一个问题,是的,你走在正确的轨道上。 (虽然我也希望看到一些其他方法来处理这类问题。)

我的一个建议是让persister演员完全不了解请求。您可以将请求作为Any类型传递。您的服务代码中的匹配器可以自动将cookie转换回Request

case class SchedulePersisted(businessObjectId: String, cookie: Any)

// in your actor
override def receive = super.receive orElse {
  case SchedulePersisted(businessObjectId, request: Request) =>
    request.complete("/businessObject/%s".format(businessObjectId))
}

关于你的第二个问题,演员课程与普通课程没有什么不同。但是你需要确保调用超类的receive方法,以便它可以处理自己的消息。我还有其他一些方法in my original answer,但我认为我更喜欢chaining partial functions like this

class SpecialHttpService extends HttpService {
  override def receive = super.receive orElse {
    case SpecialMessage(x) =>
      // handle special message
  }
}

答案 1 :(得分:0)

您也可以使用produce指令。它允许您将实际编组与请求完成分离:

get {
  produce(instanceOf[Person]) { personCompleter =>
    databaseActor ! ShowPersonJob(personCompleter)
  }
}

本例中的produce指令提取函数Person =>您可以用来透明地在业务逻辑层内透明地完成请求的单元,它不应该知道喷涂。

https://github.com/spray/spray/wiki/Marshalling-Unmarshalling